Merge "Account for the bouncer's scrim in LightBarController" into udc-dev
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 72e6645..e4e3de2 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1443,6 +1443,9 @@
         if (mActivityManagerInternal.isAppStartModeDisabled(uId, servicePkg)) {
             Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString()
                     + " -- package not allowed to start");
+            Counter.logIncrementWithUid(
+                    "job_scheduler.value_cntr_w_uid_schedule_failure_app_start_mode_disabled",
+                    uId);
             return JobScheduler.RESULT_FAILURE;
         }
 
@@ -1519,6 +1522,9 @@
                 if ((mConstants.USE_TARE_POLICY && !mTareController.canScheduleEJ(jobStatus))
                         || (!mConstants.USE_TARE_POLICY
                         && !mQuotaController.isWithinEJQuotaLocked(jobStatus))) {
+                    Counter.logIncrementWithUid(
+                            "job_scheduler.value_cntr_w_uid_schedule_failure_ej_out_of_quota",
+                            uId);
                     return JobScheduler.RESULT_FAILURE;
                 }
             }
@@ -4083,6 +4089,9 @@
                 if (!isInStateToScheduleUiJobSource && !isInStateToScheduleUiJobCalling) {
                     Slog.e(TAG, "Uid(s) " + sourceUid + "/" + callingUid
                             + " not in a state to schedule user-initiated jobs");
+                    Counter.logIncrementWithUid(
+                            "job_scheduler.value_cntr_w_uid_schedule_failure_uij_invalid_state",
+                            callingUid);
                     return JobScheduler.RESULT_FAILURE;
                 }
             }
@@ -4124,6 +4133,10 @@
                 if (namespace.isEmpty()) {
                     throw new IllegalArgumentException("namespace cannot be empty");
                 }
+                if (namespace.length() > 1000) {
+                    throw new IllegalArgumentException(
+                            "namespace cannot be more than 1000 characters");
+                }
                 namespace = namespace.intern();
             }
             return namespace;
@@ -4132,10 +4145,14 @@
         private int validateRunUserInitiatedJobsPermission(int uid, String packageName) {
             final int state = getRunUserInitiatedJobsPermissionState(uid, packageName);
             if (state == PermissionChecker.PERMISSION_HARD_DENIED) {
+                Counter.logIncrementWithUid(
+                        "job_scheduler.value_cntr_w_uid_schedule_failure_uij_no_permission", uid);
                 throw new SecurityException(android.Manifest.permission.RUN_USER_INITIATED_JOBS
                         + " required to schedule user-initiated jobs.");
             }
             if (state == PermissionChecker.PERMISSION_SOFT_DENIED) {
+                Counter.logIncrementWithUid(
+                        "job_scheduler.value_cntr_w_uid_schedule_failure_uij_no_permission", uid);
                 return JobScheduler.RESULT_FAILURE;
             }
             return JobScheduler.RESULT_SUCCESS;
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index e2ef005..b5ee895 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -4433,6 +4433,21 @@
     }
 
     /**
+     * Similar to {@link #forceStopPackageAsUser(String, int)} but will also stop the package even
+     * when the user is in the stopping state.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.FORCE_STOP_PACKAGES)
+    public void forceStopPackageAsUserEvenWhenStopping(String packageName, @UserIdInt int userId) {
+        try {
+            getService().forceStopPackageEvenWhenStopping(packageName, userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Sets the current locales of the device. Calling app must have the permission
      * {@code android.permission.CHANGE_CONFIGURATION} and
      * {@code android.permission.WRITE_SETTINGS}.
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 99ef315..e15e08f 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -102,6 +102,43 @@
     void registerUidObserver(in IUidObserver observer, int which, int cutpoint,
             String callingPackage);
     void unregisterUidObserver(in IUidObserver observer);
+
+    /**
+     * Registers a UidObserver with a uid filter.
+     *
+     * @param observer The UidObserver implementation to register.
+     * @param which    A bitmask of events to observe. See ActivityManager.UID_OBSERVER_*.
+     * @param cutpoint The cutpoint for onUidStateChanged events. When the state crosses this
+     *                 threshold in either direction, onUidStateChanged will be called.
+     * @param callingPackage The name of the calling package.
+     * @param uids     A list of uids to watch. If all uids are to be watched, use
+     *                 registerUidObserver instead.
+     * @throws RemoteException
+     * @return Returns A binder token identifying the UidObserver registration.
+     */
+    IBinder registerUidObserverForUids(in IUidObserver observer, int which, int cutpoint,
+            String callingPackage, in int[] uids);
+
+    /**
+     * Adds a uid to the list of uids that a UidObserver will receive updates about.
+     *
+     * @param observerToken  The binder token identifying the UidObserver registration.
+     * @param callingPackage The name of the calling package.
+     * @param uid            The uid to watch.
+     * @throws RemoteException
+     */
+    void addUidToObserver(in IBinder observerToken, String callingPackage, int uid);
+
+    /**
+     * Removes a uid from the list of uids that a UidObserver will receive updates about.
+     *
+     * @param observerToken  The binder token identifying the UidObserver registration.
+     * @param callingPackage The name of the calling package.
+     * @param uid            The uid to stop watching.
+     * @throws RemoteException
+     */
+    void removeUidFromObserver(in IBinder observerToken, String callingPackage, int uid);
+
     boolean isUidActive(int uid, String callingPackage);
     @JavaPassthrough(annotation=
             "@android.annotation.RequiresPermission(allOf = {android.Manifest.permission.PACKAGE_USAGE_STATS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional = true)")
@@ -299,6 +336,7 @@
     boolean registerForegroundServiceObserver(in IForegroundServiceObserver callback);
     @UnsupportedAppUsage
     void forceStopPackage(in String packageName, int userId);
+    void forceStopPackageEvenWhenStopping(in String packageName, int userId);
     boolean killPids(in int[] pids, in String reason, boolean secure);
     @UnsupportedAppUsage
     List<ActivityManager.RunningServiceInfo> getServices(int maxNum, int flags);
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index 2b15589..4d308d9 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -259,6 +259,14 @@
     boolean lockScreenWallpaperExists();
 
     /**
+     * Return true if there is a static wallpaper on the specified screen. With which=FLAG_LOCK,
+     * always return false if the lock screen doesn't run its own wallpaper engine.
+     *
+     * @hide
+     */
+    boolean isStaticWallpaper(int which);
+
+    /**
      * Temporary method for project b/197814683.
      * Return true if the lockscreen wallpaper always uses a WallpaperService, not a static image.
      * @hide
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index e53680f..df9257c 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -2887,8 +2887,9 @@
                 visitor.accept(person.getIconUri());
             }
 
-            final RemoteInputHistoryItem[] history = (RemoteInputHistoryItem[])
-                    extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
+            final RemoteInputHistoryItem[] history = extras.getParcelableArray(
+                    Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
+                    RemoteInputHistoryItem.class);
             if (history != null) {
                 for (int i = 0; i < history.length; i++) {
                     RemoteInputHistoryItem item = history[i];
@@ -2900,7 +2901,8 @@
         }
 
         if (isStyle(MessagingStyle.class) && extras != null) {
-            final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
+            final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES,
+                    Parcelable.class);
             if (!ArrayUtils.isEmpty(messages)) {
                 for (MessagingStyle.Message message : MessagingStyle.Message
                         .getMessagesFromBundleArray(messages)) {
@@ -2913,7 +2915,8 @@
                 }
             }
 
-            final Parcelable[] historic = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES);
+            final Parcelable[] historic = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES,
+                    Parcelable.class);
             if (!ArrayUtils.isEmpty(historic)) {
                 for (MessagingStyle.Message message : MessagingStyle.Message
                         .getMessagesFromBundleArray(historic)) {
@@ -2925,14 +2928,16 @@
                     }
                 }
             }
+
+            visitIconUri(visitor, extras.getParcelable(EXTRA_CONVERSATION_ICON, Icon.class));
         }
 
         if (isStyle(CallStyle.class) & extras != null) {
-            Person callPerson = extras.getParcelable(EXTRA_CALL_PERSON);
+            Person callPerson = extras.getParcelable(EXTRA_CALL_PERSON, Person.class);
             if (callPerson != null) {
                 visitor.accept(callPerson.getIconUri());
             }
-            visitIconUri(visitor, extras.getParcelable(EXTRA_VERIFICATION_ICON));
+            visitIconUri(visitor, extras.getParcelable(EXTRA_VERIFICATION_ICON, Icon.class));
         }
 
         if (mBubbleMetadata != null) {
@@ -3407,7 +3412,7 @@
      * separate object, replace it with the field's version to avoid holding duplicate copies.
      */
     private void fixDuplicateExtra(@Nullable Parcelable original, @NonNull String extraName) {
-        if (original != null && extras.getParcelable(extraName) != null) {
+        if (original != null && extras.getParcelable(extraName, Parcelable.class) != null) {
             extras.putParcelable(extraName, original);
         }
     }
@@ -7084,7 +7089,8 @@
      */
     public boolean hasImage() {
         if (isStyle(MessagingStyle.class) && extras != null) {
-            final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
+            final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES,
+                    Parcelable.class);
             if (!ArrayUtils.isEmpty(messages)) {
                 for (MessagingStyle.Message m : MessagingStyle.Message
                         .getMessagesFromBundleArray(messages)) {
@@ -8286,15 +8292,18 @@
         protected void restoreFromExtras(Bundle extras) {
             super.restoreFromExtras(extras);
 
-            mUser = extras.getParcelable(EXTRA_MESSAGING_PERSON, Person.class);
-            if (mUser == null) {
+            Person user = extras.getParcelable(EXTRA_MESSAGING_PERSON, Person.class);
+            if (user == null) {
                 CharSequence displayName = extras.getCharSequence(EXTRA_SELF_DISPLAY_NAME);
                 mUser = new Person.Builder().setName(displayName).build();
+            } else {
+                mUser = user;
             }
             mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE);
-            Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
+            Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES, Parcelable.class);
             mMessages = Message.getMessagesFromBundleArray(messages);
-            Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES);
+            Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES,
+                    Parcelable.class);
             mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
             mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION);
             mUnreadMessageCount = extras.getInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT);
@@ -11962,7 +11971,8 @@
                 if (b == null) {
                     return null;
                 }
-                Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES);
+                Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES,
+                        Parcelable.class);
                 String[] messages = null;
                 if (parcelableMessages != null) {
                     String[] tmp = new String[parcelableMessages.length];
@@ -12299,7 +12309,7 @@
     @Nullable
     private static <T extends Parcelable> T[] getParcelableArrayFromBundle(
             Bundle bundle, String key, Class<T> itemClass) {
-        final Parcelable[] array = bundle.getParcelableArray(key);
+        final Parcelable[] array = bundle.getParcelableArray(key, Parcelable.class);
         final Class<?> arrayClass = Array.newInstance(itemClass, 0).getClass();
         if (arrayClass.isInstance(array) || array == null) {
             return (T[]) array;
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 70c42d8..1603cd9 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -658,15 +658,8 @@
                     return currentWallpaper;
                 }
             }
-            if (returnDefault) {
-                Bitmap defaultWallpaper = mDefaultWallpaper;
-                if (defaultWallpaper == null || defaultWallpaper.isRecycled()) {
-                    defaultWallpaper = getDefaultWallpaper(context, which);
-                    synchronized (this) {
-                        mDefaultWallpaper = defaultWallpaper;
-                    }
-                }
-                return defaultWallpaper;
+            if (returnDefault || (which == FLAG_LOCK && isStaticWallpaper(FLAG_LOCK))) {
+                return getDefaultWallpaper(context, which);
             }
             return null;
         }
@@ -705,7 +698,7 @@
             }
             // If user wallpaper is unavailable, may be the default one instead.
             if ((dimensions == null || dimensions.width() == 0 || dimensions.height() == 0)
-                    && returnDefault) {
+                    && (returnDefault || (which == FLAG_LOCK && isStaticWallpaper(FLAG_LOCK)))) {
                 InputStream is = openDefaultWallpaper(context, which);
                 if (is != null) {
                     try {
@@ -769,18 +762,39 @@
         }
 
         private Bitmap getDefaultWallpaper(Context context, @SetWallpaperFlags int which) {
-            InputStream is = openDefaultWallpaper(context, which);
-            if (is != null) {
-                try {
-                    BitmapFactory.Options options = new BitmapFactory.Options();
-                    return BitmapFactory.decodeStream(is, null, options);
-                } catch (OutOfMemoryError e) {
+            Bitmap defaultWallpaper = mDefaultWallpaper;
+            if (defaultWallpaper == null || defaultWallpaper.isRecycled()) {
+                defaultWallpaper = null;
+                try (InputStream is = openDefaultWallpaper(context, which)) {
+                    if (is != null) {
+                        BitmapFactory.Options options = new BitmapFactory.Options();
+                        defaultWallpaper = BitmapFactory.decodeStream(is, null, options);
+                    }
+                } catch (OutOfMemoryError | IOException e) {
                     Log.w(TAG, "Can't decode stream", e);
-                } finally {
-                    IoUtils.closeQuietly(is);
                 }
             }
-            return null;
+            synchronized (this) {
+                mDefaultWallpaper = defaultWallpaper;
+            }
+            return defaultWallpaper;
+        }
+
+        /**
+         * Return true if there is a static wallpaper on the specified screen.
+         * With {@code which=}{@link #FLAG_LOCK}, always return false if the lockscreen doesn't run
+         * its own wallpaper engine.
+         */
+        private boolean isStaticWallpaper(@SetWallpaperFlags int which) {
+            if (mService == null) {
+                Log.w(TAG, "WallpaperService not running");
+                throw new RuntimeException(new DeadSystemException());
+            }
+            try {
+                return mService.isStaticWallpaper(which);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
         }
     }
 
@@ -882,21 +896,14 @@
      *     <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE}
      *     can still access the real wallpaper on all versions. </li>
      * </ul>
-     * <br>
      *
-     * Retrieve the current system wallpaper; if
-     * no wallpaper is set, the system built-in static wallpaper is returned.
-     * This is returned as an
-     * abstract Drawable that you can install in a View to display whatever
-     * wallpaper the user has currently set.
      * <p>
-     * This method can return null if there is no system wallpaper available, if
-     * wallpapers are not supported in the current user, or if the calling app is not
-     * permitted to access the system wallpaper.
+     * Equivalent to {@link #getDrawable(int)} with {@code which=}{@link #FLAG_SYSTEM}.
+     * </p>
      *
-     * @return Returns a Drawable object that will draw the system wallpaper,
-     *     or {@code null} if no system wallpaper exists or if the calling application
-     *     is not able to access the wallpaper.
+     * @return A Drawable object for the requested wallpaper.
+     *
+     * @see #getDrawable(int)
      *
      * @throws SecurityException as described in the note
      */
@@ -919,23 +926,29 @@
      *     <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE}
      *     can still access the real wallpaper on all versions. </li>
      * </ul>
-     * <br>
      *
-     * Retrieve the requested wallpaper; if
-     * no wallpaper is set, the requested built-in static wallpaper is returned.
-     * This is returned as an
-     * abstract Drawable that you can install in a View to display whatever
-     * wallpaper the user has currently set.
      * <p>
-     * This method can return null if the requested wallpaper is not available, if
-     * wallpapers are not supported in the current user, or if the calling app is not
-     * permitted to access the requested wallpaper.
+     * Retrieve the requested wallpaper for the specified wallpaper type if the wallpaper is not
+     * a live wallpaper. This method should not be used to display the user wallpaper on an app:
+     * {@link android.view.WindowManager.LayoutParams#FLAG_SHOW_WALLPAPER} should be used instead.
+     * </p>
+     * <p>
+     * When called with {@code which=}{@link #FLAG_SYSTEM},
+     * if there is a live wallpaper on home screen, the built-in default wallpaper is returned.
+     * </p>
+     * <p>
+     * When called with {@code which=}{@link #FLAG_LOCK}, if there is a live wallpaper
+     * on lock screen, or if the lock screen and home screen share the same wallpaper engine,
+     * {@code null} is returned.
+     * </p>
+     * <p>
+     * {@link #getWallpaperInfo(int)} can be used to determine whether there is a live wallpaper
+     * on a specified screen type.
+     * </p>
      *
-     * @param which The {@code FLAG_*} identifier of a valid wallpaper type.  Throws
+     * @param which The {@code FLAG_*} identifier of a valid wallpaper type. Throws
      *     IllegalArgumentException if an invalid wallpaper is requested.
-     * @return Returns a Drawable object that will draw the requested wallpaper,
-     *     or {@code null} if the requested wallpaper does not exist or if the calling application
-     *     is not able to access the wallpaper.
+     * @return A Drawable object for the requested wallpaper.
      *
      * @throws SecurityException as described in the note
      */
@@ -943,7 +956,8 @@
     @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL})
     public Drawable getDrawable(@SetWallpaperFlags int which) {
         final ColorManagementProxy cmProxy = getColorManagementProxy();
-        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, which, cmProxy);
+        boolean returnDefault = which != FLAG_LOCK;
+        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, returnDefault, which, cmProxy);
         if (bm != null) {
             Drawable dr = new BitmapDrawable(mContext.getResources(), bm);
             dr.setDither(false);
@@ -1175,15 +1189,14 @@
      *     <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE}
      *     can still access the real wallpaper on all versions. </li>
      * </ul>
-     * <br>
      *
-     * Retrieve the current system wallpaper; if there is no wallpaper set,
-     * a null pointer is returned. This is returned as an
-     * abstract Drawable that you can install in a View to display whatever
-     * wallpaper the user has currently set.
+     * <p>
+     * Equivalent to {@link #getDrawable()}.
+     * </p>
      *
-     * @return Returns a Drawable object that will draw the wallpaper or a
-     * null pointer if wallpaper is unset.
+     * @return A Drawable object for the requested wallpaper.
+     *
+     * @see #getDrawable()
      *
      * @throws SecurityException as described in the note
      */
@@ -1206,31 +1219,23 @@
      *     <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE}
      *     can still access the real wallpaper on all versions. </li>
      * </ul>
-     * <br>
      *
-     * Retrieve the requested wallpaper; if there is no wallpaper set,
-     * a null pointer is returned. This is returned as an
-     * abstract Drawable that you can install in a View to display whatever
-     * wallpaper the user has currently set.
+     * <p>
+     * Equivalent to {@link #getDrawable(int)}.
+     * </p>
      *
-     * @param which The {@code FLAG_*} identifier of a valid wallpaper type.  Throws
+     * @param which The {@code FLAG_*} identifier of a valid wallpaper type. Throws
      *     IllegalArgumentException if an invalid wallpaper is requested.
-     * @return Returns a Drawable object that will draw the wallpaper or a null pointer if
-     * wallpaper is unset.
+     * @return A Drawable object for the requested wallpaper.
+     *
+     * @see #getDrawable(int)
      *
      * @throws SecurityException as described in the note
      */
     @Nullable
     @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL})
     public Drawable peekDrawable(@SetWallpaperFlags int which) {
-        final ColorManagementProxy cmProxy = getColorManagementProxy();
-        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, which, cmProxy);
-        if (bm != null) {
-            Drawable dr = new BitmapDrawable(mContext.getResources(), bm);
-            dr.setDither(false);
-            return dr;
-        }
-        return null;
+        return getDrawable(which);
     }
 
     /**
@@ -1246,19 +1251,14 @@
      *     <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE}
      *     can still access the real wallpaper on all versions. </li>
      * </ul>
-     * <br>
      *
-     * Like {@link #getDrawable()}, but the returned Drawable has a number
-     * of limitations to reduce its overhead as much as possible. It will
-     * never scale the wallpaper (only centering it if the requested bounds
-     * do match the bitmap bounds, which should not be typical), doesn't
-     * allow setting an alpha, color filter, or other attributes, etc.  The
-     * bounds of the returned drawable will be initialized to the same bounds
-     * as the wallpaper, so normally you will not need to touch it.  The
-     * drawable also assumes that it will be used in a context running in
-     * the same density as the screen (not in density compatibility mode).
+     * <p>
+     * Equivalent to {@link #getFastDrawable(int)} with {@code which=}{@link #FLAG_SYSTEM}.
+     * </p>
      *
-     * @return Returns a Drawable object that will draw the wallpaper.
+     * @return A Drawable object for the requested wallpaper.
+     *
+     * @see #getFastDrawable(int)
      *
      * @throws SecurityException as described in the note
      */
@@ -1295,7 +1295,8 @@
      *
      * @param which The {@code FLAG_*} identifier of a valid wallpaper type.  Throws
      *     IllegalArgumentException if an invalid wallpaper is requested.
-     * @return Returns a Drawable object that will draw the wallpaper.
+     * @return An optimized Drawable object for the requested wallpaper, or {@code null}
+     *     in some cases as specified in {@link #getDrawable(int)}.
      *
      * @throws SecurityException as described in the note
      */
@@ -1303,7 +1304,8 @@
     @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL})
     public Drawable getFastDrawable(@SetWallpaperFlags int which) {
         final ColorManagementProxy cmProxy = getColorManagementProxy();
-        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, which, cmProxy);
+        boolean returnDefault = which != FLAG_LOCK;
+        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, returnDefault, which, cmProxy);
         if (bm != null) {
             return new FastBitmapDrawable(bm);
         }
@@ -1323,13 +1325,14 @@
      *     <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE}
      *     can still access the real wallpaper on all versions. </li>
      * </ul>
-     * <br>
      *
-     * Like {@link #getFastDrawable()}, but if there is no wallpaper set,
-     * a null pointer is returned.
+     * <p>
+     * Equivalent to {@link #getFastDrawable()}.
+     * </p>
      *
-     * @return Returns an optimized Drawable object that will draw the
-     * wallpaper or a null pointer if these is none.
+     * @return An optimized Drawable object for the requested wallpaper.
+     *
+     * @see #getFastDrawable()
      *
      * @throws SecurityException as described in the note
      */
@@ -1352,31 +1355,29 @@
      *     <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE}
      *     can still access the real wallpaper on all versions. </li>
      * </ul>
-     * <br>
      *
-     * Like {@link #getFastDrawable()}, but if there is no wallpaper set,
-     * a null pointer is returned.
+     * <p>
+     * Equivalent to {@link #getFastDrawable(int)}.
+     * </p>
      *
      * @param which The {@code FLAG_*} identifier of a valid wallpaper type.  Throws
      *     IllegalArgumentException if an invalid wallpaper is requested.
-     * @return Returns an optimized Drawable object that will draw the
-     * wallpaper or a null pointer if these is none.
+     * @return An optimized Drawable object for the requested wallpaper.
      *
      * @throws SecurityException as described in the note
      */
     @Nullable
     @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL})
     public Drawable peekFastDrawable(@SetWallpaperFlags int which) {
-        final ColorManagementProxy cmProxy = getColorManagementProxy();
-        Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, which, cmProxy);
-        if (bm != null) {
-            return new FastBitmapDrawable(bm);
-        }
-        return null;
+        return getFastDrawable(which);
     }
 
     /**
-     * Whether the wallpaper supports Wide Color Gamut or not.
+     * Whether the wallpaper supports Wide Color Gamut or not. This is only meant to be used by
+     * ImageWallpaper, and will always return false if the wallpaper for the specified screen
+     * is not an ImageWallpaper. This will also return false when called with {@link #FLAG_LOCK} if
+     * the lock and home screen share the same wallpaper engine.
+     *
      * @param which The wallpaper whose image file is to be retrieved. Must be a single
      *     defined kind of wallpaper, either {@link #FLAG_SYSTEM} or {@link #FLAG_LOCK}.
      * @return true when supported.
@@ -1422,7 +1423,7 @@
     }
 
     /**
-     * Like {@link #getDrawable()} but returns a Bitmap.
+     * Like {@link #getDrawable(int)} but returns a Bitmap.
      *
      * @param hardware Asks for a hardware backed bitmap.
      * @param which Specifies home or lock screen
@@ -1445,7 +1446,7 @@
     }
 
     /**
-     * Like {@link #getDrawable()} but returns a Bitmap for the provided user.
+     * Like {@link #getDrawable(int)} but returns a Bitmap for the provided user.
      *
      * @param which Specifies home or lock screen
      * @hide
@@ -1453,12 +1454,29 @@
     @TestApi
     @Nullable
     public Bitmap getBitmapAsUser(int userId, boolean hardware, @SetWallpaperFlags int which) {
+        boolean returnDefault = which != FLAG_LOCK;
+        return getBitmapAsUser(userId, hardware, which, returnDefault);
+    }
+
+    /**
+     * Overload of {@link #getBitmapAsUser(int, boolean, int)} with a returnDefault argument.
+     *
+     * @param returnDefault If true, return the default static wallpaper if no custom static
+     *                      wallpaper is set on the specified screen.
+     *                      If false, return {@code null} in that case.
+     * @hide
+     */
+    @Nullable
+    public Bitmap getBitmapAsUser(int userId, boolean hardware,
+            @SetWallpaperFlags int which, boolean returnDefault) {
         final ColorManagementProxy cmProxy = getColorManagementProxy();
-        return sGlobals.peekWallpaperBitmap(mContext, true, which, userId, hardware, cmProxy);
+        return sGlobals.peekWallpaperBitmap(mContext, returnDefault,
+                which, userId, hardware, cmProxy);
     }
 
     /**
      * Peek the dimensions of system wallpaper of the user without decoding it.
+     * Equivalent to {@link #peekBitmapDimensions(int)} with {@code which=}{@link #FLAG_SYSTEM}.
      *
      * @return the dimensions of system wallpaper
      * @hide
@@ -1472,16 +1490,45 @@
     /**
      * Peek the dimensions of given wallpaper of the user without decoding it.
      *
-     * @param which Wallpaper type. Must be either {@link #FLAG_SYSTEM} or
-     *     {@link #FLAG_LOCK}.
-     * @return the dimensions of system wallpaper
+     * <p>
+     * When called with {@code which=}{@link #FLAG_SYSTEM}, if there is a live wallpaper on
+     * home screen, the built-in default wallpaper dimensions are returned.
+     * </p>
+     * <p>
+     * When called with {@code which=}{@link #FLAG_LOCK}, if there is a live wallpaper
+     * on lock screen, or if the lock screen and home screen share the same wallpaper engine,
+     * {@code null} is returned.
+     * </p>
+     * <p>
+     * {@link #getWallpaperInfo(int)} can be used to determine whether there is a live wallpaper
+     * on a specified screen type.
+     * </p>
+     *
+     * @param which Wallpaper type. Must be either {@link #FLAG_SYSTEM} or {@link #FLAG_LOCK}.
+     * @return the dimensions of specified wallpaper
      * @hide
      */
     @TestApi
     @Nullable
     public Rect peekBitmapDimensions(@SetWallpaperFlags int which) {
+        boolean returnDefault = which != FLAG_LOCK;
+        return peekBitmapDimensions(which, returnDefault);
+    }
+
+    /**
+     * Overload of {@link #peekBitmapDimensions(int)} with a returnDefault argument.
+     *
+     * @param which Wallpaper type. Must be either {@link #FLAG_SYSTEM} or {@link #FLAG_LOCK}.
+     * @param returnDefault If true, always return the default static wallpaper dimensions
+     *                      if no custom static wallpaper is set on the specified screen.
+     *                      If false, always return {@code null} in that case.
+     * @return the dimensions of specified wallpaper
+     * @hide
+     */
+    @Nullable
+    public Rect peekBitmapDimensions(@SetWallpaperFlags int which, boolean returnDefault) {
         checkExactlyOneWallpaperFlagSet(which);
-        return sGlobals.peekWallpaperDimensions(mContext, true /* returnDefault */, which,
+        return sGlobals.peekWallpaperDimensions(mContext, returnDefault, which,
                 mContext.getUserId());
     }
 
@@ -2865,22 +2912,63 @@
             }
         }
 
-        // Check if the package exists
-        if (cn != null) {
-            try {
-                final PackageManager packageManager = context.getPackageManager();
-                packageManager.getPackageInfo(cn.getPackageName(),
-                        PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
-            } catch (PackageManager.NameNotFoundException e) {
-                cn = null;
-            }
+        if (!isComponentExist(context, cn)) {
+            cn = null;
         }
 
         return cn;
     }
 
     /**
+     * Return {@link ComponentName} of the CMF default wallpaper, or
+     * {@link #getDefaultWallpaperComponent(Context)} if none is defined.
+     *
+     * @hide
+     */
+    public static ComponentName getCmfDefaultWallpaperComponent(Context context) {
+        ComponentName cn = null;
+        String[] cmfWallpaperMap = context.getResources().getStringArray(
+                com.android.internal.R.array.cmf_default_wallpaper_component);
+        if (cmfWallpaperMap == null || cmfWallpaperMap.length == 0) {
+            Log.d(TAG, "No CMF wallpaper config");
+            return getDefaultWallpaperComponent(context);
+        }
+
+        for (String entry : cmfWallpaperMap) {
+            String[] cmfWallpaper;
+            if (!TextUtils.isEmpty(entry)) {
+                cmfWallpaper = entry.split(",");
+                if (cmfWallpaper != null && cmfWallpaper.length == 2 && VALUE_CMF_COLOR.equals(
+                        cmfWallpaper[0]) && !TextUtils.isEmpty(cmfWallpaper[1])) {
+                    cn = ComponentName.unflattenFromString(cmfWallpaper[1]);
+                    break;
+                }
+            }
+        }
+
+        if (!isComponentExist(context, cn)) {
+            cn = null;
+        }
+
+        return cn;
+    }
+
+    private static boolean isComponentExist(Context context, ComponentName cn) {
+        if (cn == null) {
+            return false;
+        }
+        try {
+            final PackageManager packageManager = context.getPackageManager();
+            packageManager.getPackageInfo(cn.getPackageName(),
+                    PackageManager.MATCH_DIRECT_BOOT_AWARE
+                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
+        } catch (PackageManager.NameNotFoundException e) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
      * Register a callback for lock wallpaper observation. Only the OS may use this.
      *
      * @return true on success; false on error.
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index e59901b..e9fb811 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -4040,8 +4040,7 @@
     public static @interface MtePolicy {}
 
     /**
-     * Called by a device owner, profile owner of an organization-owned device, or holder of the
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MTE} permission to set the Memory
+     * Called by a device owner, profile owner of an organization-owned device, to set the Memory
      * Tagging Extension (MTE) policy. MTE is a CPU extension that allows to protect against certain
      * classes of security problems at a small runtime performance cost overhead.
      *
@@ -4067,8 +4066,7 @@
     }
 
     /**
-     * Called by a device owner, profile owner of an organization-owned device, or a holder of the
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MTE} permission to
+     * Called by a device owner, profile owner of an organization-owned device to
      * get the Memory Tagging Extension (MTE) policy
      *
      * <a href="https://source.android.com/docs/security/test/memory-safety/arm-mte">
@@ -5278,9 +5276,7 @@
     }
 
     /**
-     * Called by a device admin or holder of the
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS} permission to set
-     * the password expiration timeout. Calling this method will
+     * Called by a device admin to set the password expiration timeout. Calling this method will
      * restart the countdown for password expiration for the given admin, as will changing the
      * device password (for all admins).
      * <p>
@@ -5309,10 +5305,7 @@
      * @param timeout The limit (in ms) that a password can remain in effect. A value of 0 means
      *            there is no restriction (unlimited).
      * @throws SecurityException if {@code admin} is not an active administrator or {@code admin}
-     *             does not use {@link DeviceAdminInfo#USES_POLICY_EXPIRE_PASSWORD} and the caller
-     *             does not hold the
-     *             {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS}
-     *             permission
+     *             does not use {@link DeviceAdminInfo#USES_POLICY_EXPIRE_PASSWORD}
      */
     @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     @RequiresPermission(value = MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, conditional = true)
@@ -5476,8 +5469,7 @@
      *
      * @return {@code true} if the password meets the policy requirements, {@code false} otherwise
      * @throws SecurityException if the calling application isn't an active admin that uses
-     *     {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} and does not hold the
-     *     {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS} permission
+     *     {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD}
      * @throws IllegalStateException if the user isn't unlocked
      */
     @RequiresPermission(value = MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, conditional = true)
@@ -5545,8 +5537,7 @@
      * <p>Note that when called from a profile which uses an unified challenge with its parent, the
      * screen lock complexity of the parent will be returned.
      *
-     * <p>Apps need the {@link permission#REQUEST_PASSWORD_COMPLEXITY} or
-     * {@link permission#MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS} permissions to call this
+     * <p>Apps need the {@link permission#REQUEST_PASSWORD_COMPLEXITY} permission to call this
      * method. On Android {@link android.os.Build.VERSION_CODES#S} and above, the calling
      * application does not need this permission if it is a device owner or a profile owner.
      *
@@ -5556,9 +5547,8 @@
      *
      * @throws IllegalStateException if the user is not unlocked.
      * @throws SecurityException     if the calling application does not have the permission
-     *                               {@link permission#REQUEST_PASSWORD_COMPLEXITY} or
-     *                               {@link permission#MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS}, and
-     *                               is not a device owner or a profile owner.
+     *                               {@link permission#REQUEST_PASSWORD_COMPLEXITY}, and is not a
+     *                               device owner or a profile owner.
      */
     @PasswordComplexity
     @RequiresPermission(anyOf={MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, REQUEST_PASSWORD_COMPLEXITY}, conditional = true)
@@ -5595,9 +5585,8 @@
      * with {@link #PASSWORD_QUALITY_UNSPECIFIED} on that instance prior to setting complexity
      * requirement for the managed profile.
      *
-     * @throws SecurityException if the calling application is not a device owner, a profile
-     * owner, or a holder of the
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS} permission.
+     * @throws SecurityException if the calling application is not a device owner or a profile
+     * owner.
      * @throws IllegalArgumentException if the complexity level is not one of the four above.
      * @throws IllegalStateException if the caller is trying to set password complexity while there
      * are password requirements specified using {@link #setPasswordQuality(ComponentName, int)}
@@ -5631,8 +5620,7 @@
      * restrictions on the parent profile.
      *
      * @throws SecurityException if the calling application is not a device owner or a profile
-     * owner and does not hold the
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS} permission.
+     * owner.
      */
     @PasswordComplexity
     @RequiresPermission(value = MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, conditional = true)
@@ -5744,8 +5732,7 @@
      * @return The number of times user has entered an incorrect password since the last correct
      *         password entry.
      * @throws SecurityException if the calling application does not own an active administrator
-     *             that uses {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN} and does not hold the
-     * @link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS} permission.
+     *             that uses {@link DeviceAdminInfo#USES_POLICY_WATCH_LOGIN}
      */
     @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     @RequiresPermission(value = MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, conditional = true)
@@ -5816,9 +5803,6 @@
      * profile.
      * <p>On devices not supporting {@link PackageManager#FEATURE_SECURE_LOCK_SCREEN} feature, the
      * password is always empty and this method has no effect - i.e. the policy is not set.
-     * <p>
-     * This policy can be set by holders of the
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_WIPE_DATA} permission.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the
      *              caller is not a device admin.
@@ -5991,11 +5975,9 @@
     }
 
     /**
-     * Called by a profile owner, device owner or a holder of the permission
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_RESET_PASSWORD} to provision a token
-     * which can later be used to reset the device lockscreen password (if called by on the main or
-     * system user), or managed profile challenge (if called on a managed profile), via
-     * {@link #resetPasswordWithToken}.
+     * Called by a profile or device owner to provision a token which can later be used to reset the
+     * device lockscreen password (if called by device owner), or managed profile challenge (if
+     * called by profile owner), via {@link #resetPasswordWithToken}.
      * <p>
      * If the user currently has a lockscreen password, the provisioned token will not be
      * immediately usable; it only becomes active after the user performs a confirm credential
@@ -6023,9 +6005,7 @@
      * @param token a secure token a least 32-byte long, which must be generated by a
      *        cryptographically strong random number generator.
      * @return true if the operation is successful, false otherwise.
-     * @throws SecurityException if admin is not a device or profile owner and the caller does
-     * not hold the permission
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_RESET_PASSWORD}.
+     * @throws SecurityException if admin is not a device or profile owner.
      * @throws IllegalArgumentException if the supplied token is invalid.
      */
     @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
@@ -6101,10 +6081,8 @@
     }
 
     /**
-     * Called by device owner, profile owner or a holder of the permission
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_RESET_PASSWORD}to force set a new
-     * device unlock password or a managed profile challenge on current user. This takes effect
-     * immediately.
+     * Called by device or profile owner to force set a new device unlock password or a managed
+     * profile challenge on current user. This takes effect immediately.
      * <p>
      * Unlike {@link #resetPassword}, this API can change the password even before the user or
      * device is unlocked or decrypted. The supplied token must have been previously provisioned via
@@ -6131,8 +6109,7 @@
      *        {@link #RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT}.
      * @return Returns true if the password was applied, or false if it is not acceptable for the
      *         current constraints.
-     * @throws SecurityException if admin is not a device or profile owner and the caller does not
-     * hold the permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_RESET_PASSWORD}.
+     * @throws SecurityException if admin is not a device or profile owner.
      * @throws IllegalStateException if the provided token is not valid.
      */
     @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
@@ -6168,8 +6145,7 @@
      * @param timeMs The new desired maximum time to lock in milliseconds. A value of 0 means there
      *            is no restriction.
      * @throws SecurityException if {@code admin} is not an active administrator or it does not use
-     *             {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK} and the caller does not hold the
-     *             {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCK} permission
+     *             {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK}
      */
     @RequiresPermission(value = MANAGE_DEVICE_POLICY_LOCK, conditional = true)
     public void setMaximumTimeToLock(@Nullable ComponentName admin, long timeMs) {
@@ -6214,9 +6190,7 @@
     }
 
     /**
-     * Called by a device owner, profile owner, or holder of the
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS} permission to set
-     * the timeout after which unlocking with secondary, non
+     * Called by a device/profile owner to set the timeout after which unlocking with secondary, non
      * strong auth (e.g. fingerprint, face, trust agents) times out, i.e. the user has to use a
      * strong authentication method like password, pin or pattern.
      *
@@ -6247,8 +6221,7 @@
      *         auth at all times using {@link #KEYGUARD_DISABLE_FINGERPRINT} and/or
      *         {@link #KEYGUARD_DISABLE_TRUST_AGENTS}.
      *
-     * @throws SecurityException if {@code admin} is not permitted to set this policy.
-     *
+     * @throws SecurityException if {@code admin} is not a device or profile owner.
      */
     @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     @RequiresPermission(value = MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, conditional = true)
@@ -6325,8 +6298,7 @@
      * <p>
      * This method secures the device in response to an urgent situation, such as a lost or stolen
      * device. After this method is called, the device must be unlocked using strong authentication
-     * (PIN, pattern, or password). This API is for use only by device admins and holders of the
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCK} permission.
+     * (PIN, pattern, or password). This API is intended for use only by device admins.
      * <p>
      * From version {@link android.os.Build.VERSION_CODES#R} onwards, the caller must either have
      * the LOCK_DEVICE permission or the device must have the device admin feature; if neither is
@@ -6350,8 +6322,7 @@
      * Equivalent to calling {@link #lockNow(int)} with no flags.
      *
      * @throws SecurityException if the calling application does not own an active administrator
-     *             that uses {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK} and does not hold the
-     *             {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_LOCK} permission
+     *             that uses {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK}
      */
     @RequiresPermission(value = MANAGE_DEVICE_POLICY_LOCK, conditional = true)
     public void lockNow() {
@@ -6563,8 +6534,7 @@
     }
 
     /**
-     * Callable by device owner, profile owner of an organization-owned device, or a holder of the
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_FACTORY_RESET} permission to set a
+     * Callable by device owner or profile owner of an organization-owned device, to set a
      * factory reset protection (FRP) policy. When a new policy is set, the system
      * notifies the FRP management agent of a policy change by broadcasting
      * {@code ACTION_RESET_PROTECTION_POLICY_CHANGED}.
@@ -6572,9 +6542,8 @@
      * @param admin  Which {@link DeviceAdminReceiver} this request is associated with. Null if the
      *               caller is not a device admin
      * @param policy the new FRP policy, or {@code null} to clear the current policy.
-     * @throws SecurityException if {@code admin} is not a device owner, profile owner of
-     *                           an organization-owned device, or holder of the
-     *                           {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_FACTORY_RESET} permission
+     * @throws SecurityException if {@code admin} is not a device owner or a profile owner of
+     *                           an organization-owned device.
      * @throws UnsupportedOperationException if factory reset protection is not
      *                           supported on the device.
      */
@@ -6592,10 +6561,9 @@
     }
 
     /**
-     * Callable by device owner, profile owner of an organization-owned device, or
-     * holder of the {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_FACTORY_RESET}
-     * permission to retrieve the current factory reset protection (FRP)
-     * policy set previously by {@link #setFactoryResetProtectionPolicy}.
+     * Callable by device owner or profile owner of an organization-owned device, to retrieve
+     * the current factory reset protection (FRP) policy set previously by
+     * {@link #setFactoryResetProtectionPolicy}.
      * <p>
      * This method can also be called by the FRP management agent on device or with the permission
      * {@link android.Manifest.permission#MASTER_CLEAR}, in which case, it can pass {@code null}
@@ -6605,9 +6573,7 @@
      *              {@code null} if the caller is not a device admin
      * @return The current FRP policy object or {@code null} if no policy is set.
      * @throws SecurityException if {@code admin} is not a device owner, a profile owner of
-     *                           an organization-owned device, a holder of the
-     *                           {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_FACTORY_RESET}
-     *                           permission, or the FRP management agent.
+     *                           an organization-owned device or the FRP management agent.
      * @throws UnsupportedOperationException if factory reset protection is not
      *                           supported on the device.
      */
@@ -7541,8 +7507,6 @@
      *    <li>Profile owner</li>
      *    <li>Delegated certificate installer</li>
      *    <li>Credential management app</li>
-     *    <li>An app that holds the
-     *    {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CERTIFICATES} permission</li>
      * </ul>
      *
      * <p>From Android {@link android.os.Build.VERSION_CODES#S}, the credential management app
@@ -7553,10 +7517,9 @@
      *        {@code null} if the caller is not a device admin.
      * @param alias The private key alias under which the certificate is installed.
      * @return {@code true} if the private key alias no longer exists, {@code false} otherwise.
-     * @throws SecurityException if {@code admin} is not {@code null} and not a device owner or
-     *        profile owner, or {@code admin} is null but the calling application is not a
-     *        delegated certificate installer, credential management app and does not have the
-     *        {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CERTIFICATES} permission.
+     * @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
+     *         owner, or {@code admin} is null but the calling application is not a delegated
+     *         certificate installer or credential management app.
      * @see #setDelegatedScopes
      * @see #DELEGATION_CERT_INSTALL
      */
@@ -7643,23 +7606,19 @@
      * supports these features, refer to {@link #isDeviceIdAttestationSupported()} and
      * {@link #isUniqueDeviceAttestationSupported()}.
      *
-     * <p>Device owner, profile owner, their delegated certificate installer, the credential
-     * management app or an app that holds the
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CERTIFICATES} permission can use
-     * {@link #ID_TYPE_BASE_INFO} to request inclusion of the general device information including
-     * manufacturer, model, brand, device and product in the attestation record.
-     * Only device owner, profile owner on an organization-owned device or affiliated user, their
-     * delegated certificate installers or an app that holds the
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CERTIFICATES} permission can use
-     * {@link #ID_TYPE_SERIAL}, {@link #ID_TYPE_IMEI} and {@link #ID_TYPE_MEID} to request unique
-     * device identifiers to be attested (the serial number, IMEI and MEID correspondingly),
-     * if supported by the device (see {@link #isDeviceIdAttestationSupported()}).
-     * Additionally, device owner, profile owner on an organization-owned device, their delegated
-     * certificate installers and an app that holds the
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CERTIFICATES} permission can also
-     * request the attestation record to be signed using an individual attestation certificate by
-     * specifying the {@link #ID_TYPE_INDIVIDUAL_ATTESTATION} flag (if supported by the device,
-     * see {@link #isUniqueDeviceAttestationSupported()}).
+     * <p>Device owner, profile owner, their delegated certificate installer and the credential
+     * management app can use {@link #ID_TYPE_BASE_INFO} to request inclusion of the general device
+     * information including manufacturer, model, brand, device and product in the attestation
+     * record.
+     * Only device owner, profile owner on an organization-owned device or affiliated user, and
+     * their delegated certificate installers can use {@link #ID_TYPE_SERIAL}, {@link #ID_TYPE_IMEI}
+     * and {@link #ID_TYPE_MEID} to request unique device identifiers to be attested (the serial
+     * number, IMEI and MEID correspondingly), if supported by the device
+     * (see {@link #isDeviceIdAttestationSupported()}).
+     * Additionally, device owner, profile owner on an organization-owned device and their delegated
+     * certificate installers can also request the attestation record to be signed using an
+     * individual attestation certificate by specifying the {@link #ID_TYPE_INDIVIDUAL_ATTESTATION}
+     * flag (if supported by the device, see {@link #isUniqueDeviceAttestationSupported()}).
      * <p>
      * If any of {@link #ID_TYPE_SERIAL}, {@link #ID_TYPE_IMEI} and {@link #ID_TYPE_MEID}
      * is set, it is implicitly assumed that {@link #ID_TYPE_BASE_INFO} is also set.
@@ -7684,14 +7643,12 @@
      *        If any flag is specified, then an attestation challenge must be included in the
      *        {@code keySpec}.
      * @return A non-null {@code AttestedKeyPair} if the key generation succeeded, null otherwise.
-     * @throws SecurityException if {@code admin} is not {@code null} and not a device owner or
-     *         profile owner, or {@code admin} is null but the calling application is not a
-     *         delegated certificate installer, credential management app and does not have the
-     *         {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CERTIFICATES} permission.
-     *         If Device ID attestation is requested (using {@link #ID_TYPE_SERIAL},
-     *         {@link #ID_TYPE_IMEI} or {@link #ID_TYPE_MEID}), the caller must be the Device Owner,
-     *         the Certificate Installer delegate or have the
-     *         {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CERTIFICATES} permission.
+     * @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
+     *         owner, or {@code admin} is null but the calling application is not a delegated
+     *         certificate installer or credential management app. If Device ID attestation is
+     *         requested (using {@link #ID_TYPE_SERIAL}, {@link #ID_TYPE_IMEI} or
+     *         {@link #ID_TYPE_MEID}), the caller must be the Device Owner or the Certificate
+     *         Installer delegate.
      * @throws IllegalArgumentException in the following cases:
      *         <p>
      *         <ul>
@@ -7974,8 +7931,6 @@
      *    <li>Profile owner</li>
      *    <li>Delegated certificate installer</li>
      *    <li>Credential management app</li>
-     *    <li>An app that holds the
-     *    {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CERTIFICATES} permission</li>
      * </ul>
      *
      * <p>From Android {@link android.os.Build.VERSION_CODES#S}, the credential management app
@@ -7996,10 +7951,9 @@
      *        {@link android.app.admin.DeviceAdminReceiver#onChoosePrivateKeyAlias}.
      * @return {@code true} if the provided {@code alias} exists and the certificates has been
      *        successfully associated with it, {@code false} otherwise.
-     * @throws SecurityException if {@code admin} is not {@code null} and not a device owner or
-     *        profile owner, or {@code admin} is null but the calling application is not a
-     *        delegated certificate installer, credential management app and does not have the
-     *        {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CERTIFICATES} permission.
+     * @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
+     *         owner, or {@code admin} is null but the calling application is not a delegated
+     *         certificate installer or credential management app.
      */
     @RequiresPermission(value = MANAGE_DEVICE_POLICY_CERTIFICATES, conditional = true)
     public boolean setKeyPairCertificate(@Nullable ComponentName admin,
@@ -8387,7 +8341,7 @@
      * <p>
      * This method can be called on the {@link DevicePolicyManager} instance,
      * returned by {@link #getParentProfileInstance(ComponentName)}, where the caller must be
-     * the profile owner of an organization-owned managed profile
+     * the profile owner of an organization-owned managed profile.
      * <p>
      * If the caller is device owner, then the restriction will be applied to all users. If
      * called on the parent instance, then the restriction will be applied on the personal profile.
@@ -8430,9 +8384,7 @@
      * <p>
      * This method can be called on the {@link DevicePolicyManager} instance,
      * returned by {@link #getParentProfileInstance(ComponentName)}, where the caller must be
-     * the profile owner of an organization-owned managed profile or the caller has been granted
-     * the permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CAMERA} and the
-     * cross-user permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS}.
+     * the profile owner of an organization-owned managed profile.
      *
      * @param admin The name of the admin component to check, or {@code null} to check whether any
      *              admins have disabled the camera
@@ -8483,11 +8435,9 @@
     }
 
     /**
-     * Called by a device owner, profile owner, or holder of the
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_SCREEN_CAPTURE} permission to set
-     * whether the screen capture is disabled. Disabling screen capture also prevents the
-     * content from being shown on display devices that do not have a secure video output.
-     * See {@link android.view.Display#FLAG_SECURE} for more details about
+     * Called by a device/profile owner to set whether the screen capture is disabled. Disabling
+     * screen capture also prevents the content from being shown on display devices that do not have
+     * a secure video output. See {@link android.view.Display#FLAG_SECURE} for more details about
      * secure surfaces and secure displays.
      * <p>
      * This method can be called on the {@link DevicePolicyManager} instance, returned by
@@ -8696,10 +8646,8 @@
     }
 
     /**
-     * Called by a device owner, a profile owner for the primary user, a profile
-     * owner of an organization-owned managed profile or, starting from Android
-     * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, holders of the permission
-     * {@link android.Manifest.permission#SET_TIME} to turn auto time on and off.
+     * Called by a device owner, a profile owner for the primary user or a profile
+     * owner of an organization-owned managed profile to turn auto time on and off.
      * Callers are recommended to use {@link UserManager#DISALLOW_CONFIG_DATE_TIME}
      * to prevent the user from changing this setting.
      * <p>
@@ -8711,8 +8659,7 @@
      *              caller is not a device admin.
      * @param enabled Whether time should be obtained automatically from the network or not.
      * @throws SecurityException if caller is not a device owner, a profile owner for the
-     * primary user, or a profile owner of an organization-owned managed profile or a holder of the
-     * permission {@link android.Manifest.permission#SET_TIME}.
+     * primary user, or a profile owner of an organization-owned managed profile.
      */
     @RequiresPermission(value = SET_TIME, conditional = true)
     public void setAutoTimeEnabled(@Nullable ComponentName admin, boolean enabled) {
@@ -8729,19 +8676,15 @@
     /**
      * Returns true if auto time is enabled on the device.
      *
-     * <p> Starting from Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, callers
-     * are also able to call this method if they hold the permission
-     *{@link android.Manifest.permission#SET_TIME}.
-     *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the
      *              caller is not a device admin.
      * @return true if auto time is enabled on the device.
-     * @throws SecurityException if the caller is not a device owner, a profile
-     * owner for the primary user, or a profile owner of an organization-owned managed profile or a
-     * holder of the permission {@link android.Manifest.permission#SET_TIME}.
+     * @throws SecurityException if caller is not a device owner, a profile owner for the
+     * primary user, or a profile owner of an organization-owned managed profile.
      */
     @RequiresPermission(anyOf = {SET_TIME, QUERY_ADMIN_POLICY}, conditional = true)
     public boolean getAutoTimeEnabled(@Nullable ComponentName admin) {
+        throwIfParentInstance("getAutoTimeEnabled");
         if (mService != null) {
             try {
                 return mService.getAutoTimeEnabled(admin, mContext.getPackageName());
@@ -8753,10 +8696,8 @@
     }
 
     /**
-     * Called by a device owner, a profile owner for the primary user, a profile
-     * owner of an organization-owned managed profile or, starting from Android
-     * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, holders of the permission
-     * {@link android.Manifest.permission#SET_TIME} to turn auto time zone on and off.
+     * Called by a device owner, a profile owner for the primary user or a profile
+     * owner of an organization-owned managed profile to turn auto time zone on and off.
      * Callers are recommended to use {@link UserManager#DISALLOW_CONFIG_DATE_TIME}
      * to prevent the user from changing this setting.
      * <p>
@@ -8768,8 +8709,7 @@
      *              caller is not a device admin.
      * @param enabled Whether time zone should be obtained automatically from the network or not.
      * @throws SecurityException if caller is not a device owner, a profile owner for the
-     * primary user, or a profile owner of an organization-owned managed profile or a holder of the
-     * permission {@link android.Manifest.permission#SET_TIME_ZONE}.
+     * primary user, or a profile owner of an organization-owned managed profile.
      */
     @SupportsCoexistence
     @RequiresPermission(value = SET_TIME_ZONE, conditional = true)
@@ -8787,16 +8727,11 @@
     /**
      * Returns true if auto time zone is enabled on the device.
      *
-     * <p> Starting from Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, callers
-     * are also able to call this method if they hold the permission
-     *{@link android.Manifest.permission#SET_TIME}.
-     *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the
      *              caller is not a device admin.
      * @return true if auto time zone is enabled on the device.
-     * @throws SecurityException if the caller is not a device owner, a profile
-     * owner for the primary user, or a profile owner of an organization-owned managed profile or a
-     * holder of the permission {@link android.Manifest.permission#SET_TIME_ZONE}.
+     * @throws SecurityException if caller is not a device owner, a profile owner for the
+     * primary user, or a profile owner of an organization-owned managed profile.
      */
     @RequiresPermission(anyOf = {SET_TIME_ZONE, QUERY_ADMIN_POLICY}, conditional = true)
     public boolean getAutoTimeZoneEnabled(@Nullable ComponentName admin) {
@@ -8906,8 +8841,7 @@
      *            {@link #KEYGUARD_DISABLE_IRIS},
      *            {@link #KEYGUARD_DISABLE_SHORTCUTS_ALL}.
      * @throws SecurityException if {@code admin} is not an active administrator or does not use
-     *             {@link DeviceAdminInfo#USES_POLICY_DISABLE_KEYGUARD_FEATURES} and does not hold
-     *             the {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_KEYGUARD} permission
+     *             {@link DeviceAdminInfo#USES_POLICY_DISABLE_KEYGUARD_FEATURES}
      */
     @RequiresPermission(value = MANAGE_DEVICE_POLICY_KEYGUARD, conditional = true)
     public void setKeyguardDisabledFeatures(@Nullable ComponentName admin, int which) {
@@ -9524,12 +9458,9 @@
     }
 
     /**
-     * Called by device or profile owners or holders of the permission
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_PACKAGE_STATE}.
-     * to suspend packages for this user. This function can be
-     * called by a device owner, profile owner, by a delegate given the
-     * {@link #DELEGATION_PACKAGE_ACCESS} scope via {@link #setDelegatedScopes} or by holders of the
-     * permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_PACKAGE_STATE}.
+     * Called by device or profile owners to suspend packages for this user. This function can be
+     * called by a device owner, profile owner, or by a delegate given the
+     * {@link #DELEGATION_PACKAGE_ACCESS} scope via {@link #setDelegatedScopes}.
      * <p>
      * A suspended package will not be able to start activities. Its notifications will be hidden,
      * it will not show up in recents, will not be able to show toasts or dialogs or ring the
@@ -9550,9 +9481,7 @@
      *            {@code false} the packages will be unsuspended.
      * @return an array of package names for which the suspended status is not set as requested in
      *         this method.
-     * @throws SecurityException if {@code admin} is not a device or profile owner or has not been
-     * granted the permission
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_PACKAGE_STATE}.
+     * @throws SecurityException if {@code admin} is not a device or profile owner.
      * @see #setDelegatedScopes
      * @see #DELEGATION_PACKAGE_ACCESS
      */
@@ -9912,9 +9841,7 @@
 
     /**
      * Must be called by a device owner or a profile owner of an organization-owned managed profile
-     * or holder of the permission
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_DEFAULT_SMS} to set the default SMS
-     * application.
+     * to set the default SMS application.
      * <p>
      * This method can be called on the {@link DevicePolicyManager} instance, returned by
      * {@link #getParentProfileInstance(ComponentName)}, where the caller must be the profile owner
@@ -9930,11 +9857,9 @@
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the
      *              caller is not a device admin.
      * @param packageName The name of the package to set as the default SMS application.
-     * @throws SecurityException if {@code admin} is not a device or profile owner or if
-     *                        called on the parent profile and the {@code admin} is not a
-     *                        profile owner of an organization-owned managed profile and
-     *                        if the caller has not been granted the permission
-     *                        {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_DEFAULT_SMS}.
+     * @throws SecurityException        if {@code admin} is not a device or profile owner or if
+     *                                  called on the parent profile and the {@code admin} is not a
+     *                                  profile owner of an organization-owned managed profile.
      * @throws IllegalArgumentException if called on the parent profile and the package
      *                                  provided is not a pre-installed system package.
      */
@@ -10157,8 +10082,7 @@
      *        documentation of the specific trust agent to determine the interpretation of this
      *        bundle.
      * @throws SecurityException if {@code admin} is not an active administrator or does not use
-     *             {@link DeviceAdminInfo#USES_POLICY_DISABLE_KEYGUARD_FEATURES} and does not have
-     *             the {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_KEYGUARD} permission
+     *             {@link DeviceAdminInfo#USES_POLICY_DISABLE_KEYGUARD_FEATURES}
      */
     @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
     @RequiresPermission(value = MANAGE_DEVICE_POLICY_KEYGUARD, conditional = true)
@@ -10693,20 +10617,16 @@
     }
 
     /**
-     * Called by the profile owner of a managed profile or a holder of the permission
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_PROFILE_INTERACTION}. so that some
-     * intents sent in the managed profile can also be resolved in the parent, or vice versa.
-     * Only activity intents are supported.
+     * Called by the profile owner of a managed profile so that some intents sent in the managed
+     * profile can also be resolved in the parent, or vice versa. Only activity intents are
+     * supported.
      *
-     * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the
-     *              caller is not a device admin.
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @param filter The {@link IntentFilter} the intent has to match to be also resolved in the
      *            other profile
      * @param flags {@link DevicePolicyManager#FLAG_MANAGED_CAN_ACCESS_PARENT} and
      *            {@link DevicePolicyManager#FLAG_PARENT_CAN_ACCESS_MANAGED} are supported.
-     * @throws SecurityException if {@code admin} is not a device or profile owner and is not a
-     * holder of the permission
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_PROFILE_INTERACTION}.
+     * @throws SecurityException if {@code admin} is not a device or profile owner.
      */
     @RequiresPermission(value = MANAGE_DEVICE_POLICY_PROFILE_INTERACTION, conditional = true)
     public void addCrossProfileIntentFilter(@Nullable ComponentName admin, IntentFilter filter,
@@ -10723,10 +10643,9 @@
     }
 
     /**
-     * Called by a profile owner of a managed profile or a holder of the permission
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_PROFILE_INTERACTION} to remove the
-     * cross-profile intent filters that go from the managed profile to the parent, or from the
-     * parent to the managed profile. Only removes those that have been set by the profile owner.
+     * Called by a profile owner of a managed profile to remove the cross-profile intent filters
+     * that go from the managed profile to the parent, or from the parent to the managed profile.
+     * Only removes those that have been set by the profile owner.
      * <p>
      * <em>Note</em>: A list of default cross profile intent filters are set up by the system when
      * the profile is created, some of them ensure the proper functioning of the profile, while
@@ -10735,11 +10654,8 @@
      * profile data sharing is not desired, they can be disabled with
      * {@link UserManager#DISALLOW_SHARE_INTO_MANAGED_PROFILE}.
      *
-     * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the
-     *              caller is not a device admin.
-     * @throws SecurityException if {@code admin} is not a profile owner and is not a
-     * holder of the permission
-     * @link android.Manifest.permission#MANAGE_DEVICE_POLICY_PROFILE_INTERACTION}.
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @throws SecurityException if {@code admin} is not a profile owner.
      */
     @RequiresPermission(value = MANAGE_DEVICE_POLICY_PROFILE_INTERACTION, conditional = true)
     public void clearCrossProfileIntentFilters(@Nullable ComponentName admin) {
@@ -10933,10 +10849,9 @@
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the
      *              caller is not a device admin
      * @return List of input method package names.
-     * @throws SecurityException if {@code admin} is not a device or profile owner and does not
-     *                          hold the {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_INPUT_METHODS}
-     *                          permission or if called on the parent profile and the {@code admin}
-     *                          is not a profile owner of an organization-owned managed profile.
+     * @throws SecurityException if {@code admin} is not a device, profile owner or if called on
+     *                           the parent profile and the {@code admin} is not a profile owner
+     *                           of an organization-owned managed profile.
      */
     @RequiresPermission(value = MANAGE_DEVICE_POLICY_INPUT_METHODS, conditional = true)
     public @Nullable List<String> getPermittedInputMethods(@Nullable ComponentName admin) {
@@ -11766,10 +11681,9 @@
 
     /**
      * Hide or unhide packages. When a package is hidden it is unavailable for use, but the data and
-     * actual package file remain. This function can be called by a device owner, profile owner,
-     * delegate given the {@link #DELEGATION_PACKAGE_ACCESS} scope via
-     * {@link #setDelegatedScopes}, or a holder of the
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_PACKAGE_STATE} permission.
+     * actual package file remain. This function can be called by a device owner, profile owner, or
+     * by a delegate given the {@link #DELEGATION_PACKAGE_ACCESS} scope via
+     * {@link #setDelegatedScopes}.
      * <p>
      * This method can be called on the {@link DevicePolicyManager} instance, returned by
      * {@link #getParentProfileInstance(ComponentName)}, where the caller must be the profile owner
@@ -11806,9 +11720,8 @@
 
     /**
      * Determine if a package is hidden. This function can be called by a device owner, profile
-     * owner, delegate given the {@link #DELEGATION_PACKAGE_ACCESS} scope via
-     * {@link #setDelegatedScopes}, or a holder of the
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_PACKAGE_STATE} permission.
+     * owner, or by a delegate given the {@link #DELEGATION_PACKAGE_ACCESS} scope via
+     * {@link #setDelegatedScopes}.
      * <p>
      * This method can be called on the {@link DevicePolicyManager} instance, returned by
      * {@link #getParentProfileInstance(ComponentName)}, where the caller must be the profile owner
@@ -11922,9 +11835,8 @@
     }
 
     /**
-     * Called by a device owner, profile owner or a holder of the permission
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT}
-     * to disable account management for a specific type of account.
+     * Called by a device owner or profile owner to disable account management for a specific type
+     * of account.
      * <p>
      * The calling device admin must be a device owner or profile owner. If it is not, a security
      * exception will be thrown.
@@ -11946,9 +11858,7 @@
      * @param accountType For which account management is disabled or enabled.
      * @param disabled The boolean indicating that account management will be disabled (true) or
      *            enabled (false).
-     * @throws SecurityException if {@code admin} is not a device or profile owner or has not been
-     * granted the permission
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT}.
+     * @throws SecurityException if {@code admin} is not a device or profile owner.
      */
     @RequiresPermission(value = MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT, conditional = true)
     public void setAccountManagementDisabled(@Nullable ComponentName admin, String accountType,
@@ -11986,10 +11896,6 @@
      * @see #getAccountTypesWithManagementDisabled()
      * Note that calling this method on the parent profile instance will return the same
      * value as calling it on the main {@code DevicePolicyManager} instance.
-     *
-     * @throws SecurityException if the userId is different to the caller's and the caller has not
-     * been granted {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT} and
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS}.
      * @hide
      */
     @RequiresPermission(value = MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT, conditional = true)
@@ -12415,8 +12321,7 @@
     }
 
     /**
-     * Called by a device owner, profile owner of an organization-owned managed profile, or holder
-     * of the {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_WIFI} permission to
+     * Called by a device owner or a profile owner of an organization-owned managed profile to
      * control whether the user can change networks configured by the admin. When this lockdown is
      * enabled, the user can still configure and connect to other Wi-Fi networks, or use other Wi-Fi
      * capabilities such as tethering.
@@ -12431,7 +12336,8 @@
      *                          with. Null if the caller is not a device admin.
      * @param lockdown Whether the admin configured networks should be unmodifiable by the
      *                          user.
-     * @throws SecurityException if caller is not permitted to modify this policy
+     * @throws SecurityException if caller is not a device owner or a profile owner of an
+     *                           organization-owned managed profile.
      */
     @RequiresPermission(value = MANAGE_DEVICE_POLICY_WIFI, conditional = true)
     public void setConfiguredNetworksLockdownState(
@@ -12448,16 +12354,13 @@
     }
 
     /**
-     * Called by a device owner, profile owner of an organization-owned managed profile, or holder
-     * of the {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_WIFI} permission to
+     * Called by a device owner or a profile owner of an organization-owned managed profile to
      * determine whether the user is prevented from modifying networks configured by the admin.
      *
      * @param admin             admin Which {@link DeviceAdminReceiver} this request is associated
-     *                          with. Null if the caller is not a device admin.
-     * @throws SecurityException if caller is not a device owner, a profile owner of an
-     *                           organization-owned managed profile, or holder of the
-     *                           {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_WIFI}
-     *                           permission.
+     *                          with.
+     * @throws SecurityException if caller is not a device owner or a profile owner of an
+     *                           organization-owned managed profile.
      */
     @RequiresPermission(value = MANAGE_DEVICE_POLICY_WIFI, conditional = true)
     public boolean hasLockdownAdminConfiguredNetworks(@Nullable ComponentName admin) {
@@ -12473,20 +12376,17 @@
     }
 
     /**
-     * Called by a device owner, a profile owner of an organization-owned managed
-     * profile or, starting from Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
-     * holders of the permission {@link android.Manifest.permission#SET_TIME} to set the system wall
-     * clock time. This only takes effect if called when
-     * {@link android.provider.Settings.Global#AUTO_TIME} is 0, otherwise {@code false} will be
-     * returned.
+     * Called by a device owner or a profile owner of an organization-owned managed
+     * profile to set the system wall clock time. This only takes effect if called when
+     * {@link android.provider.Settings.Global#AUTO_TIME} is 0, otherwise {@code false}
+     * will be returned.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the
      *               caller is not a device admin.
      * @param millis time in milliseconds since the Epoch
      * @return {@code true} if set time succeeded, {@code false} otherwise.
      * @throws SecurityException if {@code admin} is not a device owner or a profile owner
-     * of an organization-owned managed profile or a holder of the permission
-     * {@link android.Manifest.permission#SET_TIME}.
+     * of an organization-owned managed profile.
      */
     @RequiresPermission(value = SET_TIME, conditional = true)
     public boolean setTime(@Nullable ComponentName admin, long millis) {
@@ -12502,12 +12402,10 @@
     }
 
     /**
-     * Called by a device owner, a profile owner of an organization-owned managed
-     * profile or, starting from Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
-     * holders of the permission {@link android.Manifest.permission#SET_TIME_ZONE} to set the
-     * system's persistent default time zone. This only take effect if called when
-     * {@link android.provider.Settings.Global#AUTO_TIME_ZONE} is 0, otherwise {@code false} will be
-     * returned.
+     * Called by a device owner or a profile owner of an organization-owned managed
+     * profile to set the system's persistent default time zone. This only takes
+     * effect if called when {@link android.provider.Settings.Global#AUTO_TIME_ZONE}
+     * is 0, otherwise {@code false} will be returned.
      *
      * @see android.app.AlarmManager#setTimeZone(String)
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the
@@ -12516,8 +12414,7 @@
      *     {@link java.util.TimeZone#getAvailableIDs}
      * @return {@code true} if set timezone succeeded, {@code false} otherwise.
      * @throws SecurityException if {@code admin} is not a device owner or a profile owner
-     * of an organization-owned managed profile or a holder of the permissions
-     * {@link android.Manifest.permission#SET_TIME_ZONE}.
+     * of an organization-owned managed profile.
      */
     @RequiresPermission(value = SET_TIME_ZONE, conditional = true)
     public boolean setTimeZone(@Nullable ComponentName admin, String timeZone) {
@@ -12722,9 +12619,7 @@
      * @param packageName package to check.
      * @return true if uninstallation is blocked and the given package is visible to you, false
      *         otherwise if uninstallation isn't blocked or the given package isn't visible to you.
-     * @throws SecurityException if {@code admin} is not a device or profile owner. Starting
-     *  from {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} there will not be a security
-     *  check at all.
+     * @throws SecurityException if {@code admin} is not a device or profile owner.
      */
     public boolean isUninstallBlocked(@Nullable ComponentName admin, String packageName) {
         throwIfParentInstance("isUninstallBlocked");
@@ -12853,9 +12748,8 @@
     }
 
     /**
-     * Called by a device owner, profile owners of an organization-owned managed profile, or a
-     * holder of the {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_SYSTEM_UPDATES}
-     * permission to set a local system update policy. When a new policy is set,
+     * Called by device owners or profile owners of an organization-owned managed profile to to set
+     * a local system update policy. When a new policy is set,
      * {@link #ACTION_SYSTEM_UPDATE_POLICY_CHANGED} is broadcast.
      * <p>
      * If the supplied system update policy has freeze periods set but the freeze periods do not
@@ -12961,10 +12855,9 @@
     }
 
     /**
-     * Called by device owner, profile owner of secondary users that is affiliated with the
-     * device or a holder of the permission
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_STATUS_BAR} to disable the status
-     * bar. Disabling the status bar blocks notifications and quick settings.
+     * Called by device owner or profile owner of secondary users that is affiliated with the
+     * device to disable the status bar. Disabling the status bar blocks notifications and quick
+     * settings.
      * <p>
      * <strong>Note:</strong> This method has no effect for LockTask mode. The behavior of the
      * status bar in LockTask mode can be configured with
@@ -12979,9 +12872,8 @@
      *              caller is not a device admin.
      * @param disabled {@code true} disables the status bar, {@code false} reenables it.
      * @return {@code false} if attempting to disable the status bar failed. {@code true} otherwise.
-     * @throws SecurityException if {@code admin} is not the device owner, a profile owner of
-     * secondary user that is affiliated with the device or if the caller is not a holder of
-     * the permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_STATUS_BAR}.
+     * @throws SecurityException if {@code admin} is not the device owner, or a profile owner of
+     * secondary user that is affiliated with the device.
      * @see #isAffiliatedUser
      * @see #getSecondaryUsers
      */
@@ -13155,8 +13047,7 @@
      * cannot manage it through the UI, and {@link #PERMISSION_GRANT_STATE_GRANTED granted} in which
      * the permission is granted and the user cannot manage it through the UI. This method can only
      * be called by a profile owner, device owner, or a delegate given the
-     * {@link #DELEGATION_PERMISSION_GRANT} scope via {@link #setDelegatedScopes} or holders of the
-     * permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS} .
+     * {@link #DELEGATION_PERMISSION_GRANT} scope via {@link #setDelegatedScopes}.
      * <p/>
      * Note that user cannot manage other permissions in the affected group through the UI
      * either and their granted state will be kept as the current value. Thus, it's recommended that
@@ -13227,8 +13118,7 @@
      *            {@link #PERMISSION_GRANT_STATE_DENIED}, {@link #PERMISSION_GRANT_STATE_DEFAULT},
      *            {@link #PERMISSION_GRANT_STATE_GRANTED},
      * @return whether the permission was successfully granted or revoked.
-     * @throws SecurityException if {@code admin} is not a device or profile owner or holder of the
-     * permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS}.
+     * @throws SecurityException if {@code admin} is not a device or profile owner.
      * @see #PERMISSION_GRANT_STATE_DENIED
      * @see #PERMISSION_GRANT_STATE_DEFAULT
      * @see #PERMISSION_GRANT_STATE_GRANTED
@@ -13278,8 +13168,7 @@
      *         be one of {@link #PERMISSION_GRANT_STATE_DENIED} or
      *         {@link #PERMISSION_GRANT_STATE_GRANTED}, which indicates if the permission is
      *         currently denied or granted.
-     * @throws SecurityException if {@code admin} is not a device or profile owner or holder of the
-     * permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS}.
+     * @throws SecurityException if {@code admin} is not a device or profile owner.
      * @see #setPermissionGrantState(ComponentName, String, String, int)
      * @see PackageManager#checkPermission(String, String)
      * @see #setDelegatedScopes
@@ -13400,12 +13289,11 @@
     }
 
     /**
-     * Called by a device admin or holder of the permission
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE} to set the short
-     * support message. This will be displayed to the user in settings screens where functionality
-     * has been disabled by the admin. The message should be limited to a short statement such as
-     * "This setting is disabled by your administrator. Contact someone@example.com for support."
-     * If the message is longer than 200 characters it may be truncated.
+     * Called by a device admin to set the short support message. This will be displayed to the user
+     * in settings screens where functionality has been disabled by the admin. The message should be
+     * limited to a short statement such as "This setting is disabled by your administrator. Contact
+     * someone@example.com for support." If the message is longer than 200 characters it may be
+     * truncated.
      * <p>
      * If the short support message needs to be localized, it is the responsibility of the
      * {@link DeviceAdminReceiver} to listen to the {@link Intent#ACTION_LOCALE_CHANGED} broadcast
@@ -13416,9 +13304,7 @@
      *               caller is not a device admin.
      * @param message Short message to be displayed to the user in settings or null to clear the
      *            existing message.
-     * @throws SecurityException if {@code admin} is not an active administrator and is not a
-     * holder of the permission
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE}.
+     * @throws SecurityException if {@code admin} is not an active administrator.
      */
     @RequiresPermission(value = MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE, conditional = true)
     public void setShortSupportMessage(@Nullable ComponentName admin,
@@ -13628,9 +13514,8 @@
     }
 
     /**
-     * Called by a device owner, profile owner of an organization-owned managed profile, or holder
-     * of the {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_SECURITY_LOGGING} permission
-     * to control the security logging feature.
+     * Called by device owner or a profile owner of an organization-owned managed profile to
+     * control the security logging feature.
      *
      * <p> Security logs contain various information intended for security auditing purposes.
      * When security logging is enabled by any app other than the device owner, certain security
@@ -13667,10 +13552,8 @@
     /**
      * Return whether security logging is enabled or not by the admin.
      *
-     * <p>Can only be called by a device owner, a profile owner of an organization-owned
-     * managed profile, or a holder of the
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_SECURITY_LOGGING} permission
-     * otherwise a {@link SecurityException} will be thrown.
+     * <p>Can only be called by the device owner or a profile owner of an organization-owned
+     * managed profile, otherwise a {@link SecurityException} will be thrown.
      *
      * @param admin Which device admin this request is associated with. Null if the caller is not
      *              a device admin
@@ -13688,10 +13571,8 @@
     }
 
     /**
-     * Called by a device owner, profile owner of an organization-owned managed profile, or holder
-     * of the {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_SECURITY_LOGGING} permission
-     * to retrieve all new security logging entries since the last call to this API after device
-     * boots.
+     * Called by device owner or profile owner of an organization-owned managed profile to retrieve
+     * all new security logging entries since the last call to this API after device boots.
      *
      * <p> Access to the logs is rate limited and it will only return new logs after the admin has
      * been notified via {@link DeviceAdminReceiver#onSecurityLogsAvailable}.
@@ -13845,9 +13726,8 @@
     }
 
     /**
-     * Called by a device owner, profile owner of an organization-owned managed profile, or holder
-     * of the {@link android.Manfiest.permission#MANAGE_DEVICE_POLICY_SECURITY_LOGGING} permission
-     * to retrieve device logs from before the device's last reboot.
+     * Called by device owner or profile owner of an organization-owned managed profile to retrieve
+     * device logs from before the device's last reboot.
      * <p>
      * <strong> This API is not supported on all devices. Calling this API on unsupported devices
      * will result in {@code null} being returned. The device logs are retrieved from a RAM region
@@ -13977,9 +13857,8 @@
     }
 
     /**
-     * Called by the device owner (since API 26) or profile owner (since API 24) or holders of the
-     * permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY} to
-     * set the name of the organization under management.
+     * Called by the device owner (since API 26) or profile owner (since API 24) to set the name of
+     * the organization under management.
      *
      * <p>If the organization name needs to be localized, it is the responsibility of the caller
      * to listen to the {@link Intent#ACTION_LOCALE_CHANGED} broadcast and set a new version of this
@@ -13988,8 +13867,7 @@
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the
      *               caller is not a device admin.
      * @param title The organization name or {@code null} to clear a previously set name.
-     * @throws SecurityException if {@code admin} is not a device or profile owner or holder of the
-     * permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY}.
+     * @throws SecurityException if {@code admin} is not a device or profile owner.
      */
     @RequiresPermission(value = MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY, conditional = true)
     public void setOrganizationName(@Nullable ComponentName admin, @Nullable CharSequence title) {
@@ -15248,9 +15126,8 @@
     }
 
     /**
-     * Called by a device owner, profile owner of an organization-owned managed profile, or a holder
-     * of the {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_SYSTEM_UPDATES} permission to
-     * install a system update from the given file. The device will be
+     * Called by device owner or profile owner of an organization-owned managed profile to install
+     * a system update from the given file. The device will be
      * rebooted in order to finish installing the update. Note that if the device is rebooted, this
      * doesn't necessarily mean that the update has been applied successfully. The caller should
      * additionally check the system version with {@link android.os.Build#FINGERPRINT} or {@link
@@ -15890,9 +15767,7 @@
     }
 
     /**
-     * Called by device owner or profile owner of an organization-owned managed profile or
-     * holders of the permission
-     * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE} to toggle
+     * Called by device owner or profile owner of an organization-owned managed profile to toggle
      * Common Criteria mode for the device. When the device is in Common Criteria mode,
      * certain device functionalities are tuned to meet the higher
      * security level required by Common Criteria certification. For example:
@@ -16435,10 +16310,9 @@
     }
 
     /**
-     * Called by a device owner, profile owner of an organization-owned managed profile, or holder
-     * of the {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING}
-     * permission to enable or disable USB data signaling for the device. When disabled, USB data
-     * connections (except from charging functions) are prohibited.
+     * Called by a device owner or profile owner of an organization-owned managed profile to enable
+     * or disable USB data signaling for the device. When disabled, USB data connections
+     * (except from charging functions) are prohibited.
      *
      * <p> This API is not supported on all devices, the caller should call
      * {@link #canUsbDataSignalingBeDisabled()} to check whether enabling or disabling USB data
@@ -16584,8 +16458,7 @@
     }
 
     /**
-     * Called by a device owner, profile owner of an organization-owned managed profile, or holder
-     * of the {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_WIFI} permission to
+     * Called by device owner or profile owner of an organization-owned managed profile to
      * specify the minimum security level required for Wi-Fi networks.
      * The device may not connect to networks that do not meet the minimum security level.
      * If the current network does not meet the minimum security level set, it will be disconnected.
@@ -16629,8 +16502,7 @@
     }
 
     /**
-     * Called by device owner, profile owner of an organization-owned managed profile, or holder of
-     * the {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_WIFI} permission to
+     * Called by device owner or profile owner of an organization-owned managed profile to
      * specify the Wi-Fi SSID policy ({@link WifiSsidPolicy}).
      * Wi-Fi SSID policy specifies the SSID restriction the network must satisfy
      * in order to be eligible for a connection. Providing a null policy results in the
@@ -16658,7 +16530,8 @@
      * If the policy has not been set, it will return NULL.
      *
      * @see #setWifiSsidPolicy(WifiSsidPolicy)
-     * @throws SecurityException if the caller is not permitted to manage wifi policy
+     * @throws SecurityException if the caller is not a device owner or a profile owner on
+     * an organization-owned managed profile.
      */
     @RequiresPermission(value = MANAGE_DEVICE_POLICY_WIFI, conditional = true)
     @Nullable
diff --git a/core/java/android/app/admin/DevicePolicyResources.java b/core/java/android/app/admin/DevicePolicyResources.java
index 77ba560..f4dc6ba 100644
--- a/core/java/android/app/admin/DevicePolicyResources.java
+++ b/core/java/android/app/admin/DevicePolicyResources.java
@@ -1857,6 +1857,13 @@
             public static final String MINIRESOLVER_OPEN_IN_PERSONAL =
                     PREFIX + "MINIRESOLVER_OPEN_IN_PERSONAL";
 
+            /**
+             * Title for a dialog shown when the user has no apps capable of handling an intent
+             * in the personal profile, and must choose whether to open the intent in a
+             * cross-profile app in the work profile, or cancel. Accepts the app name as a param.
+             */
+            public static final String MINIRESOLVER_OPEN_WORK = PREFIX + "MINIRESOLVER_OPEN_WORK";
+
             public static final String MINIRESOLVER_USE_WORK_BROWSER =
                     PREFIX + "MINIRESOLVER_OPEN_IN_PERSONAL";
 
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 56f6f82..afe375c 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -981,6 +981,15 @@
      *
      * The result is returned by a callback because some constraints might take a long time
      * to evaluate.
+     *
+     * @param packageNames a list of package names to check the constraints for installation
+     * @param constraints the constraints for installation.
+     * @param executor the {@link Executor} on which to invoke the callback
+     * @param callback called when the {@link InstallConstraintsResult} is ready
+     *
+     * @throws SecurityException if the given packages' installer of record doesn't match the
+     *             caller's own package name or the installerPackageName set by the caller doesn't
+     *             match the caller's own package name.
      */
     public void checkInstallConstraints(@NonNull List<String> packageNames,
             @NonNull InstallConstraints constraints,
@@ -1008,6 +1017,8 @@
      * Note: the device idle constraint might take a long time to evaluate. The system will
      * ensure the constraint is evaluated completely before handling timeout.
      *
+     * @param packageNames a list of package names to check the constraints for installation
+     * @param constraints the constraints for installation.
      * @param callback Called when the constraints are satisfied or after timeout.
      *                 Intents sent to this callback contain:
      *                 {@link Intent#EXTRA_PACKAGES} for the input package names,
@@ -1017,6 +1028,9 @@
      *                      satisfied. Valid range is from 0 to one week. {@code 0} means the
      *                      callback will be invoked immediately no matter constraints are
      *                      satisfied or not.
+     * @throws SecurityException if the given packages' installer of record doesn't match the
+     *             caller's own package name or the installerPackageName set by the caller doesn't
+     *             match the caller's own package name.
      */
     public void waitForInstallConstraints(@NonNull List<String> packageNames,
             @NonNull InstallConstraints constraints,
@@ -1039,6 +1053,7 @@
      * may be performed on the session. In the case of timeout, you may commit the
      * session again using this method or {@link Session#commit(IntentSender)} for retries.
      *
+     * @param sessionId the session ID to commit when all constraints are satisfied.
      * @param statusReceiver Called when the state of the session changes. Intents
      *                       sent to this receiver contain {@link #EXTRA_STATUS}.
      *                       Refer to the individual status codes on how to handle them.
@@ -2565,9 +2580,9 @@
          * Sets the state of permissions for the package at installation.
          * <p/>
          * Granting any runtime permissions require the
-         * {@link android.Manifest.permission#INSTALL_GRANT_RUNTIME_PERMISSIONS} permission to be
-         * held by the caller. Revoking runtime permissions is not allowed, even during app update
-         * sessions.
+         * {@link android.Manifest.permission#INSTALL_GRANT_RUNTIME_PERMISSIONS
+         * INSTALL_GRANT_RUNTIME_PERMISSIONS} permission to be held by the caller. Revoking runtime
+         * permissions is not allowed, even during app update sessions.
          * <p/>
          * Holders without the permission are allowed to change the following special permissions:
          * <p/>
diff --git a/core/java/android/nfc/NfcAntennaInfo.java b/core/java/android/nfc/NfcAntennaInfo.java
index d54fcd2..b002ca2 100644
--- a/core/java/android/nfc/NfcAntennaInfo.java
+++ b/core/java/android/nfc/NfcAntennaInfo.java
@@ -85,8 +85,8 @@
         this.mDeviceHeight = in.readInt();
         this.mDeviceFoldable = in.readByte() != 0;
         this.mAvailableNfcAntennas = new ArrayList<>();
-        in.readParcelableList(this.mAvailableNfcAntennas,
-                AvailableNfcAntenna.class.getClassLoader());
+        in.readTypedList(this.mAvailableNfcAntennas,
+                AvailableNfcAntenna.CREATOR);
     }
 
     public static final @NonNull Parcelable.Creator<NfcAntennaInfo> CREATOR =
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 8cdb568..867dafe 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3036,9 +3036,7 @@
 
         public void destroy() {
             try {
-                // If this process is the system server process, mArray is the same object as
-                // the memory int array kept inside SettingsProvider, so skipping the close()
-                if (!Settings.isInSystemServer() && !mArray.isClosed()) {
+                if (!mArray.isClosed()) {
                     mArray.close();
                 }
             } catch (IOException e) {
@@ -3218,8 +3216,9 @@
         @UnsupportedAppUsage
         public String getStringForUser(ContentResolver cr, String name, final int userHandle) {
             final boolean isSelf = (userHandle == UserHandle.myUserId());
+            final boolean useCache = isSelf && !isInSystemServer();
             boolean needsGenerationTracker = false;
-            if (isSelf) {
+            if (useCache) {
                 synchronized (NameValueCache.this) {
                     final GenerationTracker generationTracker = mGenerationTrackers.get(name);
                     if (generationTracker != null) {
@@ -3365,9 +3364,12 @@
                                 }
                             }
                         } else {
-                            if (LOCAL_LOGV) Log.i(TAG, "call-query of user " + userHandle
-                                    + " by " + UserHandle.myUserId()
-                                    + " so not updating cache");
+                            if (DEBUG || LOCAL_LOGV) {
+                                Log.i(TAG, "call-query of user " + userHandle
+                                        + " by " + UserHandle.myUserId()
+                                        + (isInSystemServer() ? " in system_server" : "")
+                                        + " so not updating cache");
+                            }
                         }
                         return value;
                     }
@@ -5451,6 +5453,14 @@
         public static final String SHOW_TOUCHES = "show_touches";
 
         /**
+         * Show key presses and other events dispatched to focused windows on the screen.
+         * 0 = no
+         * 1 = yes
+         * @hide
+         */
+        public static final String SHOW_KEY_PRESSES = "show_key_presses";
+
+        /**
          * Log raw orientation data from
          * {@link com.android.server.policy.WindowOrientationListener} for use with the
          * orientationplot.py tool.
@@ -5840,6 +5850,7 @@
             PRIVATE_SETTINGS.add(NOTIFICATION_LIGHT_PULSE);
             PRIVATE_SETTINGS.add(POINTER_LOCATION);
             PRIVATE_SETTINGS.add(SHOW_TOUCHES);
+            PRIVATE_SETTINGS.add(SHOW_KEY_PRESSES);
             PRIVATE_SETTINGS.add(WINDOW_ORIENTATION_LISTENER_LOG);
             PRIVATE_SETTINGS.add(POWER_SOUNDS_ENABLED);
             PRIVATE_SETTINGS.add(DOCK_SOUNDS_ENABLED);
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 90e8ced..4b761c1 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -389,7 +389,7 @@
         // It's still guaranteed to have been stopped.
         // This helps with cases where the voice interaction implementation is changed
         // by the user.
-        safelyShutdownAllHotwordDetectors();
+        safelyShutdownAllHotwordDetectors(true);
     }
 
     /**
@@ -715,7 +715,7 @@
         synchronized (mLock) {
             if (!CompatChanges.isChangeEnabled(MULTIPLE_ACTIVE_HOTWORD_DETECTORS)) {
                 // Allow only one concurrent recognition via the APIs.
-                safelyShutdownAllHotwordDetectors();
+                safelyShutdownAllHotwordDetectors(false);
             } else {
                 for (HotwordDetector detector : mActiveDetectors) {
                     if (detector.isUsingSandboxedDetectionService()
@@ -878,7 +878,7 @@
         synchronized (mLock) {
             if (!CompatChanges.isChangeEnabled(MULTIPLE_ACTIVE_HOTWORD_DETECTORS)) {
                 // Allow only one concurrent recognition via the APIs.
-                safelyShutdownAllHotwordDetectors();
+                safelyShutdownAllHotwordDetectors(false);
             } else {
                 for (HotwordDetector detector : mActiveDetectors) {
                     if (!detector.isUsingSandboxedDetectionService()) {
@@ -1062,11 +1062,14 @@
         return mKeyphraseEnrollmentInfo.getKeyphraseMetadata(keyphrase, locale) != null;
     }
 
-    private void safelyShutdownAllHotwordDetectors() {
+    private void safelyShutdownAllHotwordDetectors(boolean shouldShutDownVisualQueryDetector) {
         synchronized (mLock) {
             mActiveDetectors.forEach(detector -> {
                 try {
-                    detector.destroy();
+                    if (detector != mActiveVisualQueryDetector.getInitializationDelegate()
+                            || shouldShutDownVisualQueryDetector) {
+                        detector.destroy();
+                    }
                 } catch (Exception ex) {
                     Log.i(TAG, "exception destroying HotwordDetector", ex);
                 }
@@ -1116,6 +1119,8 @@
                     pw.println();
                 });
             }
+            pw.println("Available Model Enrollment Applications:");
+            pw.println("  " + mKeyphraseEnrollmentInfo);
         }
     }
 }
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 8d84e44..230f511 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -184,6 +184,7 @@
 
     private static final long DIMMING_ANIMATION_DURATION_MS = 300L;
 
+    @GuardedBy("itself")
     private final ArrayMap<IBinder, IWallpaperEngineWrapper> mActiveEngines = new ArrayMap<>();
 
     private Handler mBackgroundHandler;
@@ -2514,10 +2515,12 @@
             // if they are visible, so we need to toggle the state to get their attention.
             if (!mEngine.mDestroyed) {
                 mEngine.detach();
-                for (IWallpaperEngineWrapper engineWrapper : mActiveEngines.values()) {
-                    if (engineWrapper.mEngine != null && engineWrapper.mEngine.mVisible) {
-                        engineWrapper.mEngine.doVisibilityChanged(false);
-                        engineWrapper.mEngine.doVisibilityChanged(true);
+                synchronized (mActiveEngines) {
+                    for (IWallpaperEngineWrapper engineWrapper : mActiveEngines.values()) {
+                        if (engineWrapper.mEngine != null && engineWrapper.mEngine.mVisible) {
+                            engineWrapper.mEngine.doVisibilityChanged(false);
+                            engineWrapper.mEngine.doVisibilityChanged(true);
+                        }
                     }
                 }
             }
@@ -2699,7 +2702,9 @@
             IWallpaperEngineWrapper engineWrapper =
                     new IWallpaperEngineWrapper(mTarget, conn, windowToken, windowType,
                             isPreview, reqWidth, reqHeight, padding, displayId, which);
-            mActiveEngines.put(windowToken, engineWrapper);
+            synchronized (mActiveEngines) {
+                mActiveEngines.put(windowToken, engineWrapper);
+            }
             if (DEBUG) {
                 Slog.v(TAG, "IWallpaperServiceWrapper Attaching window token " + windowToken);
             }
@@ -2708,7 +2713,10 @@
 
         @Override
         public void detach(IBinder windowToken) {
-            IWallpaperEngineWrapper engineWrapper = mActiveEngines.remove(windowToken);
+            IWallpaperEngineWrapper engineWrapper;
+            synchronized (mActiveEngines) {
+                engineWrapper = mActiveEngines.remove(windowToken);
+            }
             if (engineWrapper == null) {
                 Log.w(TAG, "Engine for window token " + windowToken + " already detached");
                 return;
@@ -2734,10 +2742,12 @@
     public void onDestroy() {
         Trace.beginSection("WPMS.onDestroy");
         super.onDestroy();
-        for (IWallpaperEngineWrapper engineWrapper : mActiveEngines.values()) {
-            engineWrapper.destroy();
+        synchronized (mActiveEngines) {
+            for (IWallpaperEngineWrapper engineWrapper : mActiveEngines.values()) {
+                engineWrapper.destroy();
+            }
+            mActiveEngines.clear();
         }
-        mActiveEngines.clear();
         if (mBackgroundThread != null) {
             // onDestroy might be called without a previous onCreate if WallpaperService was
             // instantiated manually. While this is a misuse of the API, some things break
@@ -2768,14 +2778,18 @@
     @Override
     protected void dump(FileDescriptor fd, PrintWriter out, String[] args) {
         out.print("State of wallpaper "); out.print(this); out.println(":");
-        for (IWallpaperEngineWrapper engineWrapper : mActiveEngines.values()) {
-            Engine engine = engineWrapper.mEngine;
-            if (engine == null) {
-                Slog.w(TAG, "Engine for wrapper " + engineWrapper + " not attached");
-                continue;
+        synchronized (mActiveEngines) {
+            for (IWallpaperEngineWrapper engineWrapper : mActiveEngines.values()) {
+                Engine engine = engineWrapper.mEngine;
+                if (engine == null) {
+                    Slog.w(TAG, "Engine for wrapper " + engineWrapper + " not attached");
+                    continue;
+                }
+                out.print("  Engine ");
+                out.print(engine);
+                out.println(":");
+                engine.dump("    ", fd, out, args);
             }
-            out.print("  Engine "); out.print(engine); out.println(":");
-            engine.dump("    ", fd, out, args);
         }
     }
 }
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index 21b14f4..428a07f 100644
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -15,12 +15,17 @@
  */
 package android.speech.tts;
 
+import static android.content.Context.DEVICE_ID_DEFAULT;
+import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RawRes;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.companion.virtual.VirtualDevice;
+import android.companion.virtual.VirtualDeviceManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -791,9 +796,48 @@
 
         mIsSystem = isSystem;
 
+        addDeviceSpecificSessionIdToParams(mContext, mParams);
         initTts();
     }
 
+    /**
+     * Add {@link VirtualDevice} specific playback audio session associated with context to
+     * parameters {@link Bundle} if applicable.
+     *
+     * @param context - {@link Context} context instance to extract the device specific audio
+     *      session id from.
+     * @param params - {@link Bundle} to add the device specific audio session id to.
+     */
+    private static void addDeviceSpecificSessionIdToParams(
+            @NonNull Context context, @NonNull Bundle params) {
+        int audioSessionId = getDeviceSpecificPlaybackSessionId(context);
+        if (audioSessionId != AUDIO_SESSION_ID_GENERATE) {
+            params.putInt(Engine.KEY_PARAM_SESSION_ID, audioSessionId);
+        }
+    }
+
+    /**
+     * Helper method to fetch {@link VirtualDevice} specific playback audio session id for given
+     * {@link Context} instance.
+     *
+     * @param context - {@link Context} to fetch the audio sesion id for.
+     * @return audio session id corresponding to {@link VirtualDevice} in case the context is
+     *      associated with {@link VirtualDevice} configured with specific audio session id,
+     *      {@link AudioManager#AUDIO_SESSION_ID_GENERATE} otherwise.
+     * @see android.companion.virtual.VirtualDeviceManager#getAudioPlaybackSessionId(int)
+     */
+    private static int getDeviceSpecificPlaybackSessionId(@NonNull Context context) {
+        int deviceId = context.getDeviceId();
+        if (deviceId == DEVICE_ID_DEFAULT) {
+            return AUDIO_SESSION_ID_GENERATE;
+        }
+        VirtualDeviceManager vdm = context.getSystemService(VirtualDeviceManager.class);
+        if (vdm == null) {
+            return AUDIO_SESSION_ID_GENERATE;
+        }
+        return vdm.getAudioPlaybackSessionId(deviceId);
+    }
+
     private <R> R runActionNoReconnect(Action<R> action, R errorResult, String method,
             boolean onlyEstablishedConnection) {
         return runAction(action, errorResult, method, false, onlyEstablishedConnection);
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 3902989..1af8ca2 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -4167,6 +4167,40 @@
     }
 
     /**
+     * Get the x coordinate of the location where the pointer should be dispatched.
+     *
+     * This is required because a mouse event, such as from a touchpad, may contain multiple
+     * pointers that should all be dispatched to the cursor position.
+     * @hide
+     */
+    public float getXDispatchLocation(int pointerIndex) {
+        if (isFromSource(InputDevice.SOURCE_MOUSE)) {
+            final float xCursorPosition = getXCursorPosition();
+            if (xCursorPosition != INVALID_CURSOR_POSITION) {
+                return xCursorPosition;
+            }
+        }
+        return getX(pointerIndex);
+    }
+
+    /**
+     * Get the y coordinate of the location where the pointer should be dispatched.
+     *
+     * This is required because a mouse event, such as from a touchpad, may contain multiple
+     * pointers that should all be dispatched to the cursor position.
+     * @hide
+     */
+    public float getYDispatchLocation(int pointerIndex) {
+        if (isFromSource(InputDevice.SOURCE_MOUSE)) {
+            final float yCursorPosition = getYCursorPosition();
+            if (yCursorPosition != INVALID_CURSOR_POSITION) {
+                return yCursorPosition;
+            }
+        }
+        return getY(pointerIndex);
+    }
+
+    /**
      * Transfer object for pointer coordinates.
      *
      * Objects of this type can be used to specify the pointer coordinates when
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 99deac4..62fdfae 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -280,7 +280,7 @@
     private static native int nativeGetLayerId(long nativeObject);
     private static native void nativeAddTransactionCommittedListener(long nativeObject,
             TransactionCommittedListener listener);
-    private static native void nativeSanitize(long transactionObject);
+    private static native void nativeSanitize(long transactionObject, int pid, int uid);
     private static native void nativeSetDestinationFrame(long transactionObj, long nativeObject,
             int l, int t, int r, int b);
     private static native void nativeSetDefaultApplyToken(IBinder token);
@@ -3960,8 +3960,8 @@
         /**
          * @hide
          */
-        public void sanitize() {
-            nativeSanitize(mNativeObject);
+        public void sanitize(int pid, int uid) {
+            nativeSanitize(mNativeObject, pid, uid);
         }
 
         /**
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 705a2ce0..6af160c 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -9845,10 +9845,11 @@
      *
      * <p><b>Note:</b> Setting the mode as {@link #IMPORTANT_FOR_AUTOFILL_NO} or
      * {@link #IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS} does not guarantee the view (and its
-     * children) will be always be considered not important; for example, when the user explicitly
-     * makes an autofill request, all views are considered important. See
-     * {@link #isImportantForAutofill()} for more details about how the View's importance for
-     * autofill is used.
+     * children) will not be used for autofill purpose; for example, when the user explicitly
+     * makes an autofill request, all views are included in the ViewStructure, and starting in
+     * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} the system uses other factors along
+     * with importance to determine the autofill behavior. See {@link #isImportantForAutofill()}
+     * for more details about how the View's importance for autofill is used.
      *
      * @param mode {@link #IMPORTANT_FOR_AUTOFILL_AUTO}, {@link #IMPORTANT_FOR_AUTOFILL_YES},
      * {@link #IMPORTANT_FOR_AUTOFILL_NO}, {@link #IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS},
@@ -9894,21 +9895,36 @@
      *   <li>otherwise, it returns {@code false}.
      * </ol>
      *
-     * <p>When a view is considered important for autofill:
-     * <ul>
-     *   <li>The view might automatically trigger an autofill request when focused on.
-     *   <li>The contents of the view are included in the {@link ViewStructure} used in an autofill
-     *       request.
-     * </ul>
-     *
-     * <p>On the other hand, when a view is considered not important for autofill:
-     * <ul>
-     *   <li>The view never automatically triggers autofill requests, but it can trigger a manual
-     *       request through {@link AutofillManager#requestAutofill(View)}.
-     *   <li>The contents of the view are not included in the {@link ViewStructure} used in an
-     *       autofill request, unless the request has the
-     *       {@link #AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS} flag.
-     * </ul>
+     * <p> The behavior of importances depends on Android version:
+     * <ol>
+     *   <li>For {@link android.os.Build.VERSION_CODES#TIRAMISU} and below:
+     *     <ol>
+     *       <li>When a view is considered important for autofill:
+     *          <ol>
+     *            <li>The view might automatically trigger an autofill request when focused on.
+     *            <li>The contents of the view are included in the {@link ViewStructure} used in an
+     *                autofill request.
+     *          </ol>
+     *        <li>On the other hand, when a view is considered not important for autofill:
+     *          <ol>
+     *            <li>The view never automatically triggers autofill requests, but it can trigger a
+     *                manual request through {@link AutofillManager#requestAutofill(View)}.
+     *            <li>The contents of the view are not included in the {@link ViewStructure} used in
+     *                an autofill request, unless the request has the
+     *                {@link #AUTOFILL_FLAG_INCLUDE_NOT_IMPORTANT_VIEWS} flag.
+     *          </ol>
+     *      </ol>
+     *  <li>For {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above:
+     *    <ol>
+     *      <li>The system uses importance, along with other view properties and other optimization
+     *          factors, to determine if a view should trigger autofill on focus.
+     *      <li>The contents of {@link #IMPORTANT_FOR_AUTOFILL_AUTO},
+     *        {@link #IMPORTANT_FOR_AUTOFILL_YES}, {@link #IMPORTANT_FOR_AUTOFILL_NO},
+     *        {@link #IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS}, and
+     *        {@link #IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS} views will be included in the
+     *        {@link ViewStructure} used in an autofill request.
+     *    </ol>
+     * </ol>
      *
      * @return whether the view is considered important for autofill.
      *
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index f5e4da8..d457847 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -2040,8 +2040,8 @@
 
     @Override
     public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
-        final float x = event.getX(pointerIndex);
-        final float y = event.getY(pointerIndex);
+        final float x = event.getXDispatchLocation(pointerIndex);
+        final float y = event.getYDispatchLocation(pointerIndex);
         if (isOnScrollbarThumb(x, y) || isDraggingScrollBar()) {
             return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_ARROW);
         }
@@ -2125,8 +2125,8 @@
         HoverTarget firstOldHoverTarget = mFirstHoverTarget;
         mFirstHoverTarget = null;
         if (!interceptHover && action != MotionEvent.ACTION_HOVER_EXIT) {
-            final float x = event.getX();
-            final float y = event.getY();
+            final float x = event.getXDispatchLocation(0);
+            final float y = event.getYDispatchLocation(0);
             final int childrenCount = mChildrenCount;
             if (childrenCount != 0) {
                 final ArrayList<View> preorderedList = buildOrderedChildList();
@@ -2347,8 +2347,8 @@
                 // Check what the child under the pointer says about the tooltip.
                 final int childrenCount = mChildrenCount;
                 if (childrenCount != 0) {
-                    final float x = event.getX();
-                    final float y = event.getY();
+                    final float x = event.getXDispatchLocation(0);
+                    final float y = event.getYDispatchLocation(0);
 
                     final ArrayList<View> preorderedList = buildOrderedChildList();
                     final boolean customOrder = preorderedList == null
@@ -2443,8 +2443,8 @@
     @Override
     protected boolean pointInHoveredChild(MotionEvent event) {
         if (mFirstHoverTarget != null) {
-            return isTransformedTouchPointInView(event.getX(), event.getY(),
-                mFirstHoverTarget.child, null);
+            return isTransformedTouchPointInView(event.getXDispatchLocation(0),
+                    event.getYDispatchLocation(0), mFirstHoverTarget.child, null);
         }
         return false;
     }
@@ -2513,8 +2513,8 @@
     public boolean onInterceptHoverEvent(MotionEvent event) {
         if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
             final int action = event.getAction();
-            final float x = event.getX();
-            final float y = event.getY();
+            final float x = event.getXDispatchLocation(0);
+            final float y = event.getYDispatchLocation(0);
             if ((action == MotionEvent.ACTION_HOVER_MOVE
                     || action == MotionEvent.ACTION_HOVER_ENTER) && isOnScrollbar(x, y)) {
                 return true;
@@ -2535,8 +2535,8 @@
         // Send the event to the child under the pointer.
         final int childrenCount = mChildrenCount;
         if (childrenCount != 0) {
-            final float x = event.getX();
-            final float y = event.getY();
+            final float x = event.getXDispatchLocation(0);
+            final float y = event.getXDispatchLocation(0);
 
             final ArrayList<View> preorderedList = buildOrderedChildList();
             final boolean customOrder = preorderedList == null
@@ -2700,10 +2700,8 @@
 
                     final int childrenCount = mChildrenCount;
                     if (newTouchTarget == null && childrenCount != 0) {
-                        final float x =
-                                isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
-                        final float y =
-                                isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);
+                        final float x = ev.getXDispatchLocation(actionIndex);
+                        final float y = ev.getYDispatchLocation(actionIndex);
                         // Find a child that can receive the event.
                         // Scan children from front to back.
                         final ArrayList<View> preorderedList = buildTouchDispatchChildList();
@@ -2757,8 +2755,8 @@
                                 } else {
                                     mLastTouchDownIndex = childIndex;
                                 }
-                                mLastTouchDownX = ev.getX();
-                                mLastTouchDownY = ev.getY();
+                                mLastTouchDownX = x;
+                                mLastTouchDownY = y;
                                 newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                 alreadyDispatchedToNewTouchTarget = true;
                                 break;
@@ -3287,7 +3285,7 @@
         if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
                 && ev.getAction() == MotionEvent.ACTION_DOWN
                 && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
-                && isOnScrollbarThumb(ev.getX(), ev.getY())) {
+                && isOnScrollbarThumb(ev.getXDispatchLocation(0), ev.getYDispatchLocation(0))) {
             return true;
         }
         return false;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 153bfde..8d74e99 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -301,6 +301,14 @@
             SystemProperties.getBoolean("persist.wm.debug.caption_on_shell", true);
 
     /**
+     * Whether the client (system UI) is handling the transient gesture and the corresponding
+     * animation.
+     * @hide
+     */
+    public static final boolean CLIENT_TRANSIENT =
+            SystemProperties.getBoolean("persist.wm.debug.client_transient", false);
+
+    /**
      * Whether the client should compute the window frame on its own.
      * @hide
      */
@@ -3772,6 +3780,16 @@
             createSyncIfNeeded();
             notifyDrawStarted(isInWMSRequestedSync());
             mDrewOnceForSync = true;
+
+            // If the active SSG is also requesting to sync a buffer, the following needs to happen
+            // 1. Ensure we keep track of the number of active syncs to know when to disable RT
+            //    RT animations that conflict with syncing a buffer.
+            // 2. Add a safeguard SSG to prevent multiple SSG that sync buffers from being submitted
+            //    out of order.
+            if (mActiveSurfaceSyncGroup != null && mSyncBuffer) {
+                updateSyncInProgressCount(mActiveSurfaceSyncGroup);
+                safeguardOverlappingSyncs(mActiveSurfaceSyncGroup);
+            }
         }
 
         if (!isViewVisible) {
@@ -3836,14 +3854,11 @@
             mWmsRequestSyncGroupState = WMS_SYNC_MERGED;
             reportDrawFinished(t, seqId);
         });
-        Trace.traceBegin(Trace.TRACE_TAG_VIEW,
-                "create WMS Sync group=" + mWmsRequestSyncGroup.getName());
         if (DEBUG_BLAST) {
             Log.d(mTag, "Setup new sync=" + mWmsRequestSyncGroup.getName());
         }
 
         mWmsRequestSyncGroup.add(this, null /* runnable */);
-        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
     }
 
     private void notifyContentCaptureEvents() {
@@ -4504,6 +4519,9 @@
             Log.d(mTag, "reportDrawFinished");
         }
 
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+            Trace.instant(Trace.TRACE_TAG_VIEW, "reportDrawFinished " + mTag + " seqId=" + seqId);
+        }
         try {
             mWindowSession.finishDrawing(mWindow, t, seqId);
         } catch (RemoteException e) {
@@ -11387,7 +11405,7 @@
      * ensure the latter SSG always waits for the former SSG's transaction to get to SF.
      */
     private void safeguardOverlappingSyncs(SurfaceSyncGroup activeSurfaceSyncGroup) {
-        SurfaceSyncGroup safeguardSsg = new SurfaceSyncGroup("VRI-Safeguard");
+        SurfaceSyncGroup safeguardSsg = new SurfaceSyncGroup("Safeguard-" + mTag);
         // Always disable timeout on the safeguard sync
         safeguardSsg.toggleTimeout(false /* enable */);
         synchronized (mPreviousSyncSafeguardLock) {
@@ -11446,8 +11464,6 @@
                     mHandler.post(runnable);
                 }
             });
-            safeguardOverlappingSyncs(mActiveSurfaceSyncGroup);
-            updateSyncInProgressCount(mActiveSurfaceSyncGroup);
             newSyncGroup = true;
         }
 
diff --git a/core/java/android/view/autofill/AutofillClientController.java b/core/java/android/view/autofill/AutofillClientController.java
index 3a8e802..2f7adaa 100644
--- a/core/java/android/view/autofill/AutofillClientController.java
+++ b/core/java/android/view/autofill/AutofillClientController.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Activity;
+import android.app.ActivityOptions;
 import android.app.Application;
 import android.content.ComponentName;
 import android.content.Intent;
@@ -486,8 +487,11 @@
     public void autofillClientAuthenticate(int authenticationId, IntentSender intent,
             Intent fillInIntent, boolean authenticateInline) {
         try {
+            ActivityOptions activityOptions = ActivityOptions.makeBasic()
+                    .setPendingIntentBackgroundActivityStartMode(
+                        ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
             mActivity.startIntentSenderForResult(intent, AUTO_FILL_AUTH_WHO_PREFIX,
-                    authenticationId, fillInIntent, 0, 0, null);
+                    authenticationId, fillInIntent, 0, 0, activityOptions.toBundle());
         } catch (IntentSender.SendIntentException e) {
             Log.e(TAG, "authenticate() failed for intent:" + intent, e);
         }
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index a6e9d4d..5d121ad 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -1464,6 +1464,13 @@
         }
 
         synchronized (mLock) {
+            if (mAllTrackedViews.contains(id)) {
+                // The id is tracked and will not trigger pre-fill request again.
+                return;
+            }
+
+            // Add the id as tracked to avoid triggering fill request again and again.
+            mAllTrackedViews.add(id);
             if (mTrackedViews != null) {
                 // To support the fill dialog can show for the autofillable Views in
                 // different pages but in the same Activity. We need to reset the
@@ -4064,11 +4071,6 @@
         }
 
         void checkViewState(AutofillId id) {
-            if (mAllTrackedViews.contains(id)) {
-                return;
-            }
-            // Add the id as tracked to avoid triggering fill request again and again.
-            mAllTrackedViews.add(id);
             if (mHasNewTrackedView) {
                 return;
             }
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index 537822e..05aff64 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -622,15 +622,11 @@
         if (b < 0) {
             b = 0;
         }
-
-        if (b + length > content.length()) {
-            length = content.length() - b;
-        }
-
+        int end = (int) Math.min((long) b + length, content.length());
         if ((flags&GET_TEXT_WITH_STYLES) != 0) {
-            return content.subSequence(b, b + length);
+            return content.subSequence(b, end);
         }
-        return TextUtils.substring(content, b, b + length);
+        return TextUtils.substring(content, b, end);
     }
 
     /**
@@ -666,13 +662,9 @@
             selEnd = tmp;
         }
 
-        int contentLength = content.length();
-        int startPos = selStart - beforeLength;
-        int endPos = selEnd + afterLength;
-
         // Guards the start and end pos within range [0, contentLength].
-        startPos = Math.max(0, startPos);
-        endPos = Math.min(contentLength, endPos);
+        int startPos = Math.max(0, selStart - beforeLength);
+        int endPos = (int) Math.min((long) selEnd + afterLength, content.length());
 
         CharSequence surroundingText;
         if ((flags & GET_TEXT_WITH_STYLES) != 0) {
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 3165654..c289506 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -726,6 +726,12 @@
                 mActions.get(i).visitUris(visitor);
             }
         }
+        if (mLandscape != null) {
+            mLandscape.visitUris(visitor);
+        }
+        if (mPortrait != null) {
+            mPortrait.visitUris(visitor);
+        }
     }
 
     private static void visitIconUri(Icon icon, @NonNull Consumer<Uri> visitor) {
diff --git a/core/java/android/window/SurfaceSyncGroup.java b/core/java/android/window/SurfaceSyncGroup.java
index 1840567..dfdff9e 100644
--- a/core/java/android/window/SurfaceSyncGroup.java
+++ b/core/java/android/window/SurfaceSyncGroup.java
@@ -53,7 +53,6 @@
  * This will also allow synchronization of surfaces across multiple processes. The caller can add
  * SurfaceControlViewHosts from another process to the SurfaceSyncGroup in a different process
  * and this clas will ensure all the surfaces are ready before applying everything together.
- * </p>
  * see the <a href="https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/window/SurfaceSyncGroup.md">SurfaceSyncGroup documentation</a>
  * </p>
  */
@@ -136,6 +135,7 @@
     @GuardedBy("mLock")
     private boolean mTimeoutDisabled;
 
+    private final String mTrackName;
 
     private static boolean isLocalBinder(IBinder binder) {
         return !(binder instanceof BinderProxy);
@@ -192,6 +192,7 @@
         }
 
         mName = name + "#" + sCounter.getAndIncrement();
+        mTrackName = "SurfaceSyncGroup " + name;
 
         mTransactionReadyConsumer = (transaction) -> {
             if (DEBUG && transaction != null) {
@@ -199,9 +200,10 @@
                         + mName);
             }
             if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
-                Trace.instant(Trace.TRACE_TAG_VIEW,
-                        "Final TransactionCallback with " + transaction + " for " + mName);
+                Trace.instantForTrack(Trace.TRACE_TAG_VIEW, mTrackName,
+                        "Final TransactionCallback with " + transaction);
             }
+            Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode());
             transactionReadyConsumer.accept(transaction);
             synchronized (mLock) {
                 // If there's a registered listener with WMS, that means we aren't actually complete
@@ -213,7 +215,7 @@
         };
 
         if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
-            Trace.instant(Trace.TRACE_TAG_VIEW, "new SurfaceSyncGroup " + mName);
+            Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_VIEW, mTrackName, mName, hashCode());
         }
 
         if (DEBUG) {
@@ -257,7 +259,7 @@
             Log.d(TAG, "markSyncReady " + mName);
         }
         if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
-            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "markSyncReady " + mName);
+            Trace.instantForTrack(Trace.TRACE_TAG_VIEW, mTrackName, "markSyncReady");
         }
         synchronized (mLock) {
             if (mHasWMSync) {
@@ -269,9 +271,6 @@
             mSyncReady = true;
             checkIfSyncIsComplete();
         }
-        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
-            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
-        }
     }
 
     /**
@@ -399,14 +398,14 @@
     public boolean add(ISurfaceSyncGroup surfaceSyncGroup, boolean parentSyncGroupMerge,
             @Nullable Runnable runnable) {
         if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
-            Trace.traceBegin(Trace.TRACE_TAG_VIEW,
-                    "addToSync token=" + mToken.hashCode() + " parent=" + mName);
+            Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_VIEW, mTrackName,
+                    "addToSync token=" + mToken.hashCode(), hashCode());
         }
         synchronized (mLock) {
             if (mSyncReady) {
                 Log.w(TAG, "Trying to add to sync when already marked as ready " + mName);
                 if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
-                    Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+                    Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode());
                 }
                 return false;
             }
@@ -419,7 +418,7 @@
         if (isLocalBinder(surfaceSyncGroup.asBinder())) {
             boolean didAddLocalSync = addLocalSync(surfaceSyncGroup, parentSyncGroupMerge);
             if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
-                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+                Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode());
             }
             return didAddLocalSync;
         }
@@ -447,7 +446,7 @@
                         mSurfaceSyncGroupCompletedListener)) {
                     mSurfaceSyncGroupCompletedListener = null;
                     if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
-                        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+                        Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode());
                     }
                     return false;
                 }
@@ -459,13 +458,13 @@
             surfaceSyncGroup.onAddedToSyncGroup(mToken, parentSyncGroupMerge);
         } catch (RemoteException e) {
             if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
-                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+                Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode());
             }
             return false;
         }
 
         if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
-            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+            Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode());
         }
         return true;
     }
@@ -510,15 +509,15 @@
                         + ". Setting up Sync in WindowManager.");
             }
             if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
-                Trace.traceBegin(Trace.TRACE_TAG_VIEW,
-                        "addSyncToWm=" + token.hashCode() + " group=" + mName);
+                Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_VIEW, mTrackName,
+                        "addSyncToWm=" + token.hashCode(), hashCode());
             }
             AddToSurfaceSyncGroupResult addToSyncGroupResult = new AddToSurfaceSyncGroupResult();
             if (!WindowManagerGlobal.getWindowManagerService().addToSurfaceSyncGroup(token,
                     parentSyncGroupMerge, surfaceSyncGroupCompletedListener,
                     addToSyncGroupResult)) {
                 if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
-                    Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+                    Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode());
                 }
                 return false;
             }
@@ -527,12 +526,12 @@
                     addToSyncGroupResult.mTransactionReadyCallback);
         } catch (RemoteException e) {
             if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
-                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+                Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode());
             }
             return false;
         }
         if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
-            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+            Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode());
         }
         return true;
     }
@@ -550,8 +549,8 @@
         }
 
         if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
-            Trace.traceBegin(Trace.TRACE_TAG_VIEW,
-                    "addLocalSync=" + childSurfaceSyncGroup.mName + " parent=" + mName);
+            Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_VIEW, mTrackName,
+                    "addLocalSync=" + childSurfaceSyncGroup.mName, hashCode());
         }
         ITransactionReadyCallback callback =
                 createTransactionReadyCallback(parentSyncGroupMerge);
@@ -562,7 +561,7 @@
 
         childSurfaceSyncGroup.setTransactionCallbackFromParent(mISurfaceSyncGroup, callback);
         if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
-            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+            Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode());
         }
         return true;
     }
@@ -574,9 +573,9 @@
         }
 
         if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
-            Trace.traceBegin(Trace.TRACE_TAG_VIEW,
+            Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_VIEW, mTrackName,
                     "setTransactionCallbackFromParent " + mName + " callback="
-                            + transactionReadyCallback.hashCode());
+                            + transactionReadyCallback.hashCode(), hashCode());
         }
 
         // Start the timeout when this SurfaceSyncGroup has been added to a parent SurfaceSyncGroup.
@@ -617,9 +616,9 @@
                 mParentSyncGroup = parentSyncGroup;
                 mTransactionReadyConsumer = (transaction) -> {
                     if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
-                        Trace.traceBegin(Trace.TRACE_TAG_VIEW,
-                                "transactionReadyCallback " + mName + " callback="
-                                        + transactionReadyCallback.hashCode());
+                        Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_VIEW, mTrackName,
+                                "Invoke transactionReadyCallback="
+                                        + transactionReadyCallback.hashCode(), hashCode());
                     }
                     lastCallback.accept(null);
 
@@ -629,7 +628,7 @@
                         transaction.apply();
                     }
                     if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
-                        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+                        Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode());
                     }
                 };
                 addedToSyncListener = mAddedToSyncListener;
@@ -647,7 +646,7 @@
             addedToSyncListener.run();
         }
         if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
-            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+            Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode());
         }
     }
 
@@ -669,8 +668,8 @@
         }
 
         if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
-            Trace.instant(Trace.TRACE_TAG_VIEW,
-                    "checkIfSyncIsComplete " + mName + " mSyncReady=" + mSyncReady
+            Trace.instantForTrack(Trace.TRACE_TAG_VIEW, mTrackName,
+                    "checkIfSyncIsComplete mSyncReady=" + mSyncReady
                             + " mPendingSyncs=" + mPendingSyncs.size());
         }
 
@@ -715,6 +714,7 @@
                     public void onTransactionReady(Transaction t) {
                         synchronized (mLock) {
                             if (t != null) {
+                                t.sanitize(Binder.getCallingPid(), Binder.getCallingUid());
                                 // When an older parent sync group is added due to a child syncGroup
                                 // getting added to multiple groups, we need to maintain merge order
                                 // so the older parentSyncGroup transactions are overwritten by
@@ -726,9 +726,8 @@
                             }
                             mPendingSyncs.remove(this);
                             if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
-                                Trace.instant(Trace.TRACE_TAG_VIEW,
-                                        "onTransactionReady group=" + mName + " callback="
-                                                + hashCode());
+                                Trace.instantForTrack(Trace.TRACE_TAG_VIEW, mTrackName,
+                                        "onTransactionReady callback=" + hashCode());
                             }
                             checkIfSyncIsComplete();
                         }
@@ -743,8 +742,8 @@
             }
             mPendingSyncs.add(transactionReadyCallback);
             if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
-                Trace.instant(Trace.TRACE_TAG_VIEW,
-                        "createTransactionReadyCallback " + mName + " mPendingSyncs="
+                Trace.instantForTrack(Trace.TRACE_TAG_VIEW, mTrackName,
+                        "createTransactionReadyCallback mPendingSyncs="
                                 + mPendingSyncs.size() + " transactionReady="
                                 + transactionReadyCallback.hashCode());
             }
@@ -764,13 +763,12 @@
         public boolean onAddedToSyncGroup(IBinder parentSyncGroupToken,
                 boolean parentSyncGroupMerge) {
             if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
-                Trace.traceBegin(Trace.TRACE_TAG_VIEW,
-                        "onAddedToSyncGroup token=" + parentSyncGroupToken.hashCode() + " child="
-                                + mName);
+                Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_VIEW, mTrackName,
+                        "onAddedToSyncGroup token=" + parentSyncGroupToken.hashCode(), hashCode());
             }
             boolean didAdd = addSyncToWm(parentSyncGroupToken, parentSyncGroupMerge, null);
             if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
-                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+                Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_VIEW, mTrackName, hashCode());
             }
             return didAdd;
         }
diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
index 6bc7ac6..1f7640d 100644
--- a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
+++ b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
@@ -149,11 +149,13 @@
      *
      * @param mode The activated magnification mode.
      * @param duration The duration in milliseconds during the magnification is activated.
+     * @param scale The last magnification scale for the activation
      */
-    public static void logMagnificationUsageState(int mode, long duration) {
+    public static void logMagnificationUsageState(int mode, long duration, float scale) {
         FrameworkStatsLog.write(FrameworkStatsLog.MAGNIFICATION_USAGE_REPORTED,
                 convertToLoggingMagnificationMode(mode),
-                duration);
+                duration,
+                convertToLoggingMagnificationScale(scale));
     }
 
     /**
@@ -254,4 +256,8 @@
                 return MAGNIFICATION_USAGE_REPORTED__ACTIVATED_MODE__MAGNIFICATION_UNKNOWN_MODE;
         }
     }
+
+    private static int convertToLoggingMagnificationScale(float scale) {
+        return (int) (scale * 100);
+    }
 }
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index 44d517a..904fb66 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -19,7 +19,7 @@
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_PERSONAL;
 import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_WORK;
-import static android.app.admin.DevicePolicyResources.Strings.Core.MINIRESOLVER_OPEN_IN_WORK;
+import static android.app.admin.DevicePolicyResources.Strings.Core.MINIRESOLVER_OPEN_WORK;
 import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
@@ -227,8 +227,8 @@
 
     private String getOpenInWorkMessage(CharSequence targetLabel) {
         return getSystemService(DevicePolicyManager.class).getResources().getString(
-                MINIRESOLVER_OPEN_IN_WORK,
-                () -> getString(R.string.miniresolver_open_in_work, targetLabel),
+                MINIRESOLVER_OPEN_WORK,
+                () -> getString(R.string.miniresolver_open_work, targetLabel),
                 targetLabel);
     }
 
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index c144503..f277635 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -305,10 +305,17 @@
     private final SparseArray<ActionProperties> mActionPropertiesMap = new SparseArray<>();
     @GuardedBy("mLock")
     private boolean mEnabled;
+    private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener =
+            this::updateProperties;
 
     // Wrapping this in a holder class achieves lazy loading behavior
     private static final class SLatencyTrackerHolder {
-        private static final LatencyTracker sLatencyTracker = new LatencyTracker();
+        private static final LatencyTracker sLatencyTracker;
+
+        static {
+            sLatencyTracker = new LatencyTracker();
+            sLatencyTracker.startListeningForLatencyTrackerConfigChanges();
+        }
     }
 
     public static LatencyTracker getInstance(Context context) {
@@ -319,31 +326,16 @@
      * Constructor for LatencyTracker
      *
      * <p>This constructor is only visible for test classes to inject their own consumer callbacks
+     *
+     * @param startListeningForPropertyChanges If set, constructor will register for device config
+     *                      property updates prior to returning. If not set,
+     *                      {@link #startListeningForLatencyTrackerConfigChanges} must be called
+     *                      to start listening.
      */
     @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
     @VisibleForTesting
     public LatencyTracker() {
         mEnabled = DEFAULT_ENABLED;
-
-        final Context context = ActivityThread.currentApplication();
-        if (context != null
-                && context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) == PERMISSION_GRANTED) {
-            // Post initialization to the background in case we're running on the main thread.
-            BackgroundThread.getHandler().post(() -> this.updateProperties(
-                    DeviceConfig.getProperties(NAMESPACE_LATENCY_TRACKER)));
-            DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_LATENCY_TRACKER,
-                    BackgroundThread.getExecutor(), this::updateProperties);
-        } else {
-            if (DEBUG) {
-                if (context == null) {
-                    Log.d(TAG, "No application for " + ActivityThread.currentActivityThread());
-                } else {
-                    Log.d(TAG, "Initialized the LatencyTracker."
-                            + " (No READ_DEVICE_CONFIG permission to change configs)"
-                            + " enabled=" + mEnabled + ", package=" + context.getPackageName());
-                }
-            }
-        }
     }
 
     private void updateProperties(DeviceConfig.Properties properties) {
@@ -366,6 +358,54 @@
     }
 
     /**
+     * Test method to start listening to {@link DeviceConfig} properties changes.
+     *
+     * <p>During testing, a {@link LatencyTracker} it is desired to stop and start listening for
+     * config updates.
+     *
+     * <p>This is not used for production usages of this class outside of testing as we are
+     * using a single static object.
+     */
+    @VisibleForTesting
+    public void startListeningForLatencyTrackerConfigChanges() {
+        final Context context = ActivityThread.currentApplication();
+        if (context != null
+                && context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) == PERMISSION_GRANTED) {
+            // Post initialization to the background in case we're running on the main thread.
+            BackgroundThread.getHandler().post(() -> this.updateProperties(
+                    DeviceConfig.getProperties(NAMESPACE_LATENCY_TRACKER)));
+            DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_LATENCY_TRACKER,
+                    BackgroundThread.getExecutor(), mOnPropertiesChangedListener);
+        } else {
+            if (DEBUG) {
+                if (context == null) {
+                    Log.d(TAG, "No application for " + ActivityThread.currentActivityThread());
+                } else {
+                    synchronized (mLock) {
+                        Log.d(TAG, "Initialized the LatencyTracker."
+                                + " (No READ_DEVICE_CONFIG permission to change configs)"
+                                + " enabled=" + mEnabled + ", package=" + context.getPackageName());
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Test method to stop listening to {@link DeviceConfig} properties changes.
+     *
+     * <p>During testing, a {@link LatencyTracker} it is desired to stop and start listening for
+     * config updates.
+     *
+     * <p>This is not used for production usages of this class outside of testing as we are
+     * using a single static object.
+     */
+    @VisibleForTesting
+    public void stopListeningForLatencyTrackerConfigChanges() {
+        DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener);
+    }
+
+    /**
      * A helper method to translate action type to name.
      *
      * @param atomsProtoAction the action type defined in AtomsProto.java
diff --git a/core/java/com/android/internal/view/RecyclerViewCaptureHelper.java b/core/java/com/android/internal/view/RecyclerViewCaptureHelper.java
index 8192ffd..f4e9e30 100644
--- a/core/java/com/android/internal/view/RecyclerViewCaptureHelper.java
+++ b/core/java/com/android/internal/view/RecyclerViewCaptureHelper.java
@@ -105,6 +105,13 @@
         }
 
         if (recyclerView.requestChildRectangleOnScreen(anchor, input, true)) {
+            if (anchor.getParent() == null) {
+                // BUG(b/239050369): Check if the tracked anchor view is still attached.
+                Log.w(TAG, "Bug: anchor view " + anchor + " is detached after scrolling");
+                resultConsumer.accept(result); // empty result
+                return;
+            }
+
             int scrolled = prevAnchorTop - anchor.getTop(); // inverse of movement
             mScrollDelta += scrolled; // view.top-- is equivalent to parent.scrollY++
             result.scrollDelta = mScrollDelta;
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 193099b..4249253 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -972,9 +972,9 @@
     SurfaceComposerClient::Transaction::sendSurfaceFlushJankDataTransaction(ctrl);
 }
 
-static void nativeSanitize(JNIEnv* env, jclass clazz, jlong transactionObj) {
+static void nativeSanitize(JNIEnv* env, jclass clazz, jlong transactionObj, jint pid, jint uid) {
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
-    transaction->sanitize();
+    transaction->sanitize(pid, uid);
 }
 
 static void nativeSetDestinationFrame(JNIEnv* env, jclass clazz, jlong transactionObj,
@@ -2268,7 +2268,7 @@
             (void*) nativeSetTrustedPresentationCallback },
     {"nativeClearTrustedPresentationCallback", "(JJ)V",
             (void*) nativeClearTrustedPresentationCallback },
-    {"nativeSanitize", "(J)V",
+    {"nativeSanitize", "(JII)V",
             (void*) nativeSanitize },
     {"nativeSetDestinationFrame", "(JJIIII)V",
                 (void*)nativeSetDestinationFrame },
diff --git a/core/proto/android/providers/settings/system.proto b/core/proto/android/providers/settings/system.proto
index 7503dde4..48243f2 100644
--- a/core/proto/android/providers/settings/system.proto
+++ b/core/proto/android/providers/settings/system.proto
@@ -68,6 +68,7 @@
         // orientationplot.py tool.
         // 0 = no, 1 = yes
         optional SettingProto window_orientation_listener_log = 3 [ (android.privacy).dest = DEST_AUTOMATIC ];
+        optional SettingProto show_key_presses = 4 [ (android.privacy).dest = DEST_AUTOMATIC ];
     }
     optional DevOptions developer_options = 7;
 
diff --git a/core/res/res/drawable/focus_event_pressed_key_background.xml b/core/res/res/drawable/focus_event_pressed_key_background.xml
new file mode 100644
index 0000000..e069f0b
--- /dev/null
+++ b/core/res/res/drawable/focus_event_pressed_key_background.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:name="focus_event_pressed_key_background"
+       android:shape="rectangle">
+
+  <!-- View background color -->
+  <solid
+      android:color="#AA000000" >
+  </solid>
+
+  <!-- View border color and width -->
+  <stroke
+      android:width="2dp"
+      android:color="@android:color/white">
+  </stroke>
+
+  <!-- The radius makes the corners rounded -->
+  <corners
+      android:radius="8dp">
+  </corners>
+
+</shape>
\ No newline at end of file
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 17d8402..984e2ca 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1392,9 +1392,6 @@
     <!-- Number of notifications to keep in the notification service historical archive -->
     <integer name="config_notificationServiceArchiveSize">100</integer>
 
-    <!-- List of packages that will be able to use full screen intent in notifications by default -->
-    <string-array name="config_useFullScreenIntentPackages" translatable="false" />
-
     <!-- Allow the menu hard key to be disabled in LockScreen on some devices -->
     <bool name="config_disableMenuKeyInLockScreen">false</bool>
 
@@ -1820,6 +1817,16 @@
          specified -->
     <string name="default_wallpaper_component" translatable="false">@null</string>
 
+    <!-- CMF colors to default wallpaper component map, the component with color matching the device
+         color will be the cmf default wallpapers. The default wallpaper will be default wallpaper
+         component if not specified.
+
+         E.g. for SLV color, and com.android.example/com.android.example.SlVDefaultWallpaper
+         <item>SLV,com.android.example/com.android.example.SlVDefaultWallpaper</item> -->
+    <string-array name="cmf_default_wallpaper_component" translatable="false">
+        <!-- Add packages here -->
+    </string-array>
+
     <!-- By default a product has no distinct default lock wallpaper -->
     <item name="default_lock_wallpaper" type="drawable">@null</item>
 
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index a49ea0b..027d4f8 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5912,11 +5912,12 @@
 
     <!-- Error message. This text lets the user know that their current personal apps don't support the specific content. [CHAR LIMIT=NONE] -->
     <string name="resolver_no_personal_apps_available">No personal apps</string>
-
+    <!-- Dialog title. User must choose to open content in a cross-profile app or cancel. [CHAR LIMIT=NONE] -->
+    <string name="miniresolver_open_work">Open work <xliff:g id="app" example="YouTube">%s</xliff:g>?</string>
     <!-- Dialog title. User must choose between opening content in a cross-profile app or same-profile browser. [CHAR LIMIT=NONE] -->
-    <string name="miniresolver_open_in_personal">Open personal <xliff:g id="app" example="YouTube">%s</xliff:g></string>
+    <string name="miniresolver_open_in_personal">Open in personal <xliff:g id="app" example="YouTube">%s</xliff:g>?</string>
     <!-- Dialog title. User must choose between opening content in a cross-profile app or same-profile browser. [CHAR LIMIT=NONE] -->
-    <string name="miniresolver_open_in_work">Open work <xliff:g id="app" example="YouTube">%s</xliff:g></string>
+    <string name="miniresolver_open_in_work">Open in work <xliff:g id="app" example="YouTube">%s</xliff:g>?</string>
     <!-- Button option. Open the link in the personal browser. [CHAR LIMIT=NONE] -->
     <string name="miniresolver_use_personal_browser">Use personal browser</string>
     <!-- Button option. Open the link in the work browser. [CHAR LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index e3697bb..73e3b41 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1576,6 +1576,7 @@
   <java-symbol type="id" name="open_cross_profile" />
   <java-symbol type="string" name="miniresolver_open_in_personal" />
   <java-symbol type="string" name="miniresolver_open_in_work" />
+  <java-symbol type="string" name="miniresolver_open_work" />
   <java-symbol type="string" name="miniresolver_use_personal_browser" />
   <java-symbol type="string" name="miniresolver_use_work_browser" />
   <java-symbol type="id" name="button_open" />
@@ -2020,7 +2021,6 @@
   <java-symbol type="integer" name="config_notificationsBatteryMediumARGB" />
   <java-symbol type="integer" name="config_notificationsBatteryNearlyFullLevel" />
   <java-symbol type="integer" name="config_notificationServiceArchiveSize" />
-  <java-symbol type="array" name="config_useFullScreenIntentPackages" />
   <java-symbol type="integer" name="config_previousVibrationsDumpLimit" />
   <java-symbol type="integer" name="config_defaultVibrationAmplitude" />
   <java-symbol type="dimen" name="config_hapticChannelMaxVibrationAmplitude" />
@@ -2110,6 +2110,7 @@
   <java-symbol type="string" name="data_usage_rapid_body" />
   <java-symbol type="string" name="data_usage_rapid_app_body" />
   <java-symbol type="string" name="default_wallpaper_component" />
+  <java-symbol type="array" name="cmf_default_wallpaper_component" />
   <java-symbol type="string" name="device_storage_monitor_notification_channel" />
   <java-symbol type="string" name="dlg_ok" />
   <java-symbol type="string" name="dump_heap_notification" />
@@ -5119,4 +5120,6 @@
   <java-symbol type="style" name="ThemeOverlay.DeviceDefault.Accent" />
   <java-symbol type="style" name="ThemeOverlay.DeviceDefault.Accent.Light" />
   <java-symbol type="style" name="ThemeOverlay.DeviceDefault.Dark.ActionBar.Accent" />
+
+  <java-symbol type="drawable" name="focus_event_pressed_key_background" />
 </resources>
diff --git a/core/tests/coretests/src/android/view/ViewGroupTest.java b/core/tests/coretests/src/android/view/ViewGroupTest.java
index 506cc2d..b37c8fd 100644
--- a/core/tests/coretests/src/android/view/ViewGroupTest.java
+++ b/core/tests/coretests/src/android/view/ViewGroupTest.java
@@ -20,6 +20,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
@@ -87,6 +88,9 @@
         viewGroup.dispatchTouchEvent(event);
         verify(viewB).dispatchTouchEvent(event);
 
+        viewGroup.onResolvePointerIcon(event, 0 /* pointerIndex */);
+        verify(viewB).onResolvePointerIcon(event, 0);
+
         event = MotionEvent.obtain(0 /* downTime */, 0 /* eventTime */,
                 MotionEvent.ACTION_POINTER_DOWN | (1 << MotionEvent.ACTION_POINTER_INDEX_SHIFT),
                 2 /* pointerCount */, properties, coords, 0 /* metaState */, 0 /* buttonState */,
@@ -95,7 +99,11 @@
         viewGroup.dispatchTouchEvent(event);
         verify(viewB).dispatchTouchEvent(event);
 
+        viewGroup.onResolvePointerIcon(event, 1 /* pointerIndex */);
+        verify(viewB).onResolvePointerIcon(event, 1);
+
         verify(viewA, never()).dispatchTouchEvent(any());
+        verify(viewA, never()).onResolvePointerIcon(any(), anyInt());
     }
 
     /**
diff --git a/core/tests/coretests/src/android/view/inputmethod/BaseInputConnectionTest.java b/core/tests/coretests/src/android/view/inputmethod/BaseInputConnectionTest.java
index b3886e8..f04f603 100644
--- a/core/tests/coretests/src/android/view/inputmethod/BaseInputConnectionTest.java
+++ b/core/tests/coretests/src/android/view/inputmethod/BaseInputConnectionTest.java
@@ -549,6 +549,14 @@
                                 .isEqualTo(new SurroundingText("456", 0, 3, -1)))
                 .isTrue();
 
+        verifyContentEquals(mBaseInputConnection.getTextBeforeCursor(Integer.MAX_VALUE, 0), "123");
+        verifyContentEquals(mBaseInputConnection.getTextAfterCursor(Integer.MAX_VALUE, 0), "789");
+        assertThat(
+                mBaseInputConnection
+                        .getSurroundingText(Integer.MAX_VALUE, Integer.MAX_VALUE, 0)
+                        .isEqualTo(new SurroundingText("123456789", 3, 6, -1)))
+                .isTrue();
+
         int cursorCapsMode =
                 TextUtils.getCapsMode(
                         "123456789",
@@ -618,6 +626,45 @@
     }
 
     @Test
+    public void testGetText_emptyText() {
+        // ""
+        prepareContent("", 0, 0, -1, -1);
+
+        verifyContentEquals(mBaseInputConnection.getTextBeforeCursor(1, 0), "");
+        verifyContentEquals(mBaseInputConnection.getTextAfterCursor(1, 0), "");
+        assertThat(mBaseInputConnection.getSelectedText(0)).isNull();
+
+        // This falls back to default implementation in {@code InputConnection}, which always return
+        // -1 for offset.
+        assertThat(
+                mBaseInputConnection
+                        .getSurroundingText(1, 1, 0)
+                        .isEqualTo(new SurroundingText("", 0, 0, -1)))
+                .isTrue();
+
+        verifyContentEquals(mBaseInputConnection.getTextBeforeCursor(0, 0), "");
+        verifyContentEquals(mBaseInputConnection.getTextAfterCursor(0, 0), "");
+        assertThat(mBaseInputConnection.getSelectedText(0)).isNull();
+        // This falls back to default implementation in {@code InputConnection}, which always return
+        // -1 for offset.
+        assertThat(
+                mBaseInputConnection
+                        .getSurroundingText(0, 0, 0)
+                        .isEqualTo(new SurroundingText("", 0, 0, -1)))
+                .isTrue();
+
+        verifyContentEquals(mBaseInputConnection.getTextBeforeCursor(Integer.MAX_VALUE, 0), "");
+        verifyContentEquals(mBaseInputConnection.getTextAfterCursor(Integer.MAX_VALUE, 0), "");
+        assertThat(mBaseInputConnection.getSelectedText(0)).isNull();
+        assertThat(
+                mBaseInputConnection
+                        .getSurroundingText(Integer.MAX_VALUE, Integer.MAX_VALUE, 0)
+                        .isEqualTo(new SurroundingText("", 0, 0, -1)))
+                .isTrue();
+    }
+
+
+    @Test
     public void testReplaceText_toEditorWithoutSelectionAndComposing() {
         // before replace: "|"
         // after replace: "text1|"
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
index 31c5a76..963014e 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java
@@ -24,6 +24,10 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
 import android.app.ActivityOptions;
 import android.app.PendingIntent;
@@ -33,6 +37,8 @@
 import android.graphics.Bitmap;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Binder;
 import android.os.Looper;
@@ -58,6 +64,7 @@
 import java.util.Arrays;
 import java.util.Map;
 import java.util.concurrent.CountDownLatch;
+import java.util.function.Consumer;
 
 /**
  * Tests for RemoteViews.
@@ -703,4 +710,61 @@
             return null;
         }
     }
+
+    @Test
+    public void visitUris() {
+        RemoteViews views = new RemoteViews(mPackage, R.layout.remote_views_test);
+
+        final Uri imageUri = Uri.parse("content://media/image");
+        final Icon icon1 = Icon.createWithContentUri("content://media/icon1");
+        final Icon icon2 = Icon.createWithContentUri("content://media/icon2");
+        final Icon icon3 = Icon.createWithContentUri("content://media/icon3");
+        final Icon icon4 = Icon.createWithContentUri("content://media/icon4");
+        views.setImageViewUri(R.id.image, imageUri);
+        views.setTextViewCompoundDrawables(R.id.text, icon1, icon2, icon3, icon4);
+
+        Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class);
+        views.visitUris(visitor);
+        verify(visitor, times(1)).accept(eq(imageUri));
+        verify(visitor, times(1)).accept(eq(icon1.getUri()));
+        verify(visitor, times(1)).accept(eq(icon2.getUri()));
+        verify(visitor, times(1)).accept(eq(icon3.getUri()));
+        verify(visitor, times(1)).accept(eq(icon4.getUri()));
+    }
+
+    @Test
+    public void visitUris_separateOrientation() {
+        final RemoteViews landscape = new RemoteViews(mPackage, R.layout.remote_views_test);
+        final Uri imageUriL = Uri.parse("content://landscape/image");
+        final Icon icon1L = Icon.createWithContentUri("content://landscape/icon1");
+        final Icon icon2L = Icon.createWithContentUri("content://landscape/icon2");
+        final Icon icon3L = Icon.createWithContentUri("content://landscape/icon3");
+        final Icon icon4L = Icon.createWithContentUri("content://landscape/icon4");
+        landscape.setImageViewUri(R.id.image, imageUriL);
+        landscape.setTextViewCompoundDrawables(R.id.text, icon1L, icon2L, icon3L, icon4L);
+
+        final RemoteViews portrait = new RemoteViews(mPackage, 33);
+        final Uri imageUriP = Uri.parse("content://portrait/image");
+        final Icon icon1P = Icon.createWithContentUri("content://portrait/icon1");
+        final Icon icon2P = Icon.createWithContentUri("content://portrait/icon2");
+        final Icon icon3P = Icon.createWithContentUri("content://portrait/icon3");
+        final Icon icon4P = Icon.createWithContentUri("content://portrait/icon4");
+        portrait.setImageViewUri(R.id.image, imageUriP);
+        portrait.setTextViewCompoundDrawables(R.id.text, icon1P, icon2P, icon3P, icon4P);
+
+        RemoteViews views = new RemoteViews(landscape, portrait);
+
+        Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class);
+        views.visitUris(visitor);
+        verify(visitor, times(1)).accept(eq(imageUriL));
+        verify(visitor, times(1)).accept(eq(icon1L.getUri()));
+        verify(visitor, times(1)).accept(eq(icon2L.getUri()));
+        verify(visitor, times(1)).accept(eq(icon3L.getUri()));
+        verify(visitor, times(1)).accept(eq(icon4L.getUri()));
+        verify(visitor, times(1)).accept(eq(imageUriP));
+        verify(visitor, times(1)).accept(eq(icon1P.getUri()));
+        verify(visitor, times(1)).accept(eq(icon2P.getUri()));
+        verify(visitor, times(1)).accept(eq(icon3P.getUri()));
+        verify(visitor, times(1)).accept(eq(icon4P.getUri()));
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java b/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java
index 645324d..584ad20 100644
--- a/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java
@@ -28,12 +28,12 @@
 import android.provider.DeviceConfig;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
 
 import com.android.internal.util.LatencyTracker.ActionProperties;
 
 import com.google.common.truth.Expect;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -48,7 +48,6 @@
 import java.util.Map;
 import java.util.stream.Collectors;
 
-@SmallTest
 @RunWith(AndroidJUnit4.class)
 public class LatencyTrackerTest {
     private static final String ENUM_NAME_PREFIX = "UIACTION_LATENCY_REPORTED__ACTION__";
@@ -65,6 +64,11 @@
         mLatencyTracker = FakeLatencyTracker.create();
     }
 
+    @After
+    public void tearDown() {
+        mLatencyTracker.stopListeningForLatencyTrackerConfigChanges();
+    }
+
     @Test
     public void testCujsMapToEnumsCorrectly() {
         List<Field> actions = getAllActionFields();
diff --git a/core/tests/coretests/testdoubles/src/com/android/internal/util/FakeLatencyTracker.java b/core/tests/coretests/testdoubles/src/com/android/internal/util/FakeLatencyTracker.java
index 61e976b..76e69bf 100644
--- a/core/tests/coretests/testdoubles/src/com/android/internal/util/FakeLatencyTracker.java
+++ b/core/tests/coretests/testdoubles/src/com/android/internal/util/FakeLatencyTracker.java
@@ -25,8 +25,6 @@
 import android.util.Log;
 import android.util.SparseArray;
 
-import androidx.annotation.Nullable;
-
 import com.android.internal.annotations.GuardedBy;
 
 import com.google.common.collect.ImmutableMap;
@@ -51,15 +49,17 @@
     private final List<String> mPerfettoTraceNamesTriggered;
     private final AtomicReference<SparseArray<ActionProperties>> mLastPropertiesUpdate =
             new AtomicReference<>();
-    @Nullable
-    @GuardedBy("mLock")
-    private Callable<Boolean> mShouldClosePropertiesUpdatedCallable = null;
+    private final AtomicReference<Callable<Boolean>> mShouldClosePropertiesUpdatedCallable =
+            new AtomicReference<>();
     private final ConditionVariable mDeviceConfigPropertiesUpdated = new ConditionVariable();
 
     public static FakeLatencyTracker create() throws Exception {
         Log.i(TAG, "create");
         disableForAllActions();
+        Log.i(TAG, "done disabling all actions");
         FakeLatencyTracker fakeLatencyTracker = new FakeLatencyTracker();
+        Log.i(TAG, "done creating tracker object");
+        fakeLatencyTracker.startListeningForLatencyTrackerConfigChanges();
         // always return the fake in the disabled state and let the client control the desired state
         fakeLatencyTracker.waitForGlobalEnabledState(false);
         fakeLatencyTracker.waitForAllPropertiesEnableState(false);
@@ -131,27 +131,25 @@
     @Override
     public void onDeviceConfigPropertiesUpdated(SparseArray<ActionProperties> actionProperties) {
         Log.d(TAG, "onDeviceConfigPropertiesUpdated: " + actionProperties);
+
         mLastPropertiesUpdate.set(actionProperties);
-        synchronized (mLock) {
-            if (mShouldClosePropertiesUpdatedCallable != null) {
-                try {
-                    boolean shouldClosePropertiesUpdated =
-                            mShouldClosePropertiesUpdatedCallable.call();
-                    Log.i(TAG, "shouldClosePropertiesUpdatedCallable callable result="
-                            + shouldClosePropertiesUpdated);
-                    if (shouldClosePropertiesUpdated) {
-                        Log.i(TAG, "shouldClosePropertiesUpdatedCallable=true, opening condition");
-                        mShouldClosePropertiesUpdatedCallable = null;
-                        mDeviceConfigPropertiesUpdated.open();
-                    }
-                } catch (Exception e) {
-                    Log.e(TAG, "exception when calling callable", e);
-                    throw new RuntimeException(e);
+        Callable<Boolean> shouldClosePropertiesUpdated =
+                mShouldClosePropertiesUpdatedCallable.get();
+        if (shouldClosePropertiesUpdated != null) {
+            try {
+                boolean result = shouldClosePropertiesUpdated.call();
+                Log.i(TAG, "shouldClosePropertiesUpdatedCallable callable result=" + result);
+                if (result) {
+                    mShouldClosePropertiesUpdatedCallable.set(null);
+                    mDeviceConfigPropertiesUpdated.open();
                 }
-            } else {
-                Log.i(TAG, "no conditional callable set, opening condition");
-                mDeviceConfigPropertiesUpdated.open();
+            } catch (Exception e) {
+                Log.e(TAG, "exception when calling callable", e);
+                throw new RuntimeException(e);
             }
+        } else {
+            Log.i(TAG, "no conditional callable set, opening condition");
+            mDeviceConfigPropertiesUpdated.open();
         }
     }
 
@@ -175,107 +173,82 @@
 
     public void waitForAllPropertiesEnableState(boolean enabledState) throws Exception {
         Log.i(TAG, "waitForAllPropertiesEnableState: enabledState=" + enabledState);
-        synchronized (mLock) {
-            Log.i(TAG, "closing condition");
-            mDeviceConfigPropertiesUpdated.close();
-            // Update the callable to only close the properties updated condition when all the
-            // desired properties have been updated. The DeviceConfig callbacks may happen multiple
-            // times so testing the resulting updates is required.
-            mShouldClosePropertiesUpdatedCallable = () -> {
-                Log.i(TAG, "verifying if last properties update has all properties enable="
-                        + enabledState);
-                SparseArray<ActionProperties> newProperties = mLastPropertiesUpdate.get();
-                if (newProperties != null) {
-                    for (int i = 0; i < newProperties.size(); i++) {
-                        if (newProperties.get(i).isEnabled() != enabledState) {
-                            return false;
-                        }
+        // Update the callable to only close the properties updated condition when all the
+        // desired properties have been updated. The DeviceConfig callbacks may happen multiple
+        // times so testing the resulting updates is required.
+        waitForPropertiesCondition(() -> {
+            Log.i(TAG, "verifying if last properties update has all properties enable="
+                    + enabledState);
+            SparseArray<ActionProperties> newProperties = mLastPropertiesUpdate.get();
+            if (newProperties != null) {
+                for (int i = 0; i < newProperties.size(); i++) {
+                    if (newProperties.get(i).isEnabled() != enabledState) {
+                        return false;
                     }
                 }
-                return true;
-            };
-            if (mShouldClosePropertiesUpdatedCallable.call()) {
-                return;
             }
-        }
-        Log.i(TAG, "waiting for condition");
-        mDeviceConfigPropertiesUpdated.block();
+            return true;
+        });
     }
 
     public void waitForMatchingActionProperties(ActionProperties actionProperties)
             throws Exception {
         Log.i(TAG, "waitForMatchingActionProperties: actionProperties=" + actionProperties);
-        synchronized (mLock) {
-            Log.i(TAG, "closing condition");
-            mDeviceConfigPropertiesUpdated.close();
-            // Update the callable to only close the properties updated condition when all the
-            // desired properties have been updated. The DeviceConfig callbacks may happen multiple
-            // times so testing the resulting updates is required.
-            mShouldClosePropertiesUpdatedCallable = () -> {
-                Log.i(TAG, "verifying if last properties update contains matching property ="
-                        + actionProperties);
-                SparseArray<ActionProperties> newProperties = mLastPropertiesUpdate.get();
-                if (newProperties != null) {
-                    if (newProperties.size() > 0) {
-                        return newProperties.get(actionProperties.getAction()).equals(
-                                actionProperties);
-                    }
+        // Update the callable to only close the properties updated condition when all the
+        // desired properties have been updated. The DeviceConfig callbacks may happen multiple
+        // times so testing the resulting updates is required.
+        waitForPropertiesCondition(() -> {
+            Log.i(TAG, "verifying if last properties update contains matching property ="
+                    + actionProperties);
+            SparseArray<ActionProperties> newProperties = mLastPropertiesUpdate.get();
+            if (newProperties != null) {
+                if (newProperties.size() > 0) {
+                    return newProperties.get(actionProperties.getAction()).equals(
+                            actionProperties);
                 }
-                return false;
-            };
-            if (mShouldClosePropertiesUpdatedCallable.call()) {
-                return;
             }
-        }
-        Log.i(TAG, "waiting for condition");
-        mDeviceConfigPropertiesUpdated.block();
+            return false;
+        });
     }
 
     public void waitForActionEnabledState(int action, boolean enabledState) throws Exception {
         Log.i(TAG, "waitForActionEnabledState:"
                 + " action=" + action + ", enabledState=" + enabledState);
-        synchronized (mLock) {
-            Log.i(TAG, "closing condition");
-            mDeviceConfigPropertiesUpdated.close();
-            // Update the callable to only close the properties updated condition when all the
-            // desired properties have been updated. The DeviceConfig callbacks may happen multiple
-            // times so testing the resulting updates is required.
-            mShouldClosePropertiesUpdatedCallable = () -> {
-                Log.i(TAG, "verifying if last properties update contains action=" + action
-                        + ", enabledState=" + enabledState);
-                SparseArray<ActionProperties> newProperties = mLastPropertiesUpdate.get();
-                if (newProperties != null) {
-                    if (newProperties.size() > 0) {
-                        return newProperties.get(action).isEnabled() == enabledState;
-                    }
+        // Update the callable to only close the properties updated condition when all the
+        // desired properties have been updated. The DeviceConfig callbacks may happen multiple
+        // times so testing the resulting updates is required.
+        waitForPropertiesCondition(() -> {
+            Log.i(TAG, "verifying if last properties update contains action=" + action
+                    + ", enabledState=" + enabledState);
+            SparseArray<ActionProperties> newProperties = mLastPropertiesUpdate.get();
+            if (newProperties != null) {
+                if (newProperties.size() > 0) {
+                    return newProperties.get(action).isEnabled() == enabledState;
                 }
-                return false;
-            };
-            if (mShouldClosePropertiesUpdatedCallable.call()) {
-                return;
             }
-        }
-        Log.i(TAG, "waiting for condition");
-        mDeviceConfigPropertiesUpdated.block();
+            return false;
+        });
     }
 
     public void waitForGlobalEnabledState(boolean enabledState) throws Exception {
         Log.i(TAG, "waitForGlobalEnabledState: enabledState=" + enabledState);
-        synchronized (mLock) {
-            Log.i(TAG, "closing condition");
-            mDeviceConfigPropertiesUpdated.close();
-            // Update the callable to only close the properties updated condition when all the
-            // desired properties have been updated. The DeviceConfig callbacks may happen multiple
-            // times so testing the resulting updates is required.
-            mShouldClosePropertiesUpdatedCallable = () -> {
-                //noinspection deprecation
-                return isEnabled() == enabledState;
-            };
-            if (mShouldClosePropertiesUpdatedCallable.call()) {
-                return;
-            }
+        // Update the callable to only close the properties updated condition when all the
+        // desired properties have been updated. The DeviceConfig callbacks may happen multiple
+        // times so testing the resulting updates is required.
+        waitForPropertiesCondition(() -> {
+            //noinspection deprecation
+            return isEnabled() == enabledState;
+        });
+    }
+
+    public void waitForPropertiesCondition(Callable<Boolean> shouldClosePropertiesUpdatedCallable)
+            throws Exception {
+        mShouldClosePropertiesUpdatedCallable.set(shouldClosePropertiesUpdatedCallable);
+        mDeviceConfigPropertiesUpdated.close();
+        if (!shouldClosePropertiesUpdatedCallable.call()) {
+            Log.i(TAG, "waiting for mDeviceConfigPropertiesUpdated condition");
+            mDeviceConfigPropertiesUpdated.block();
         }
-        Log.i(TAG, "waiting for condition");
-        mDeviceConfigPropertiesUpdated.block();
+        Log.i(TAG, "waitForPropertiesCondition: returning");
     }
 }
diff --git a/libs/WindowManager/Shell/res/drawable-night/reachability_education_ic_left_hand.xml b/libs/WindowManager/Shell/res/drawable-night/reachability_education_ic_left_hand.xml
new file mode 100644
index 0000000..fbcf6d7
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable-night/reachability_education_ic_left_hand.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="20dp"
+        android:height="20dp"
+        android:viewportWidth="960"
+        android:viewportHeight="960">
+    <group android:scaleX="-1" android:translateX="960">
+        <path
+            android:fillColor="?android:attr/textColorSecondary"
+            android:pathData="M432.46,48Q522,48 585,110.92Q648,173.83 648,264Q648,314 627.5,358.5Q607,403 566,432L528,432L528,370Q551,349 563.5,321.5Q576,294 576,263.78Q576,204.39 534,162.2Q492,120 432,120Q372,120 330,162Q288,204 288,264.31Q288,295 300,323Q312,351 336,370L336,456Q280,430 248,378Q216,326 216,264Q216,173.83 278.97,110.92Q341.94,48 432.46,48ZM414,864Q399.53,864 386.77,859Q374,854 363,843L144,624L211,557Q225,543 243,538Q261,533 279,538L336,552L336,288Q337,248 364.57,220Q392.14,192 432.07,192Q472,192 500,219.84Q528,247.68 528,288L528,432L576,432Q576,432 576,432Q576,432 576,432L715,497Q744,511 758,538Q772,565 767,596L737,802Q732,828 711.76,846Q691.52,864 666,864L414,864ZM414,792L666,792L698,569Q698,569 698,569Q698,569 698,569L559,504L456,504L456,288Q456,278 449,271Q442,264 432,264Q422,264 415,271Q408,278 408,288L408,644L262,608L246,624L414,792ZM666,792L414,792L414,792L414,792L414,792L414,792Q414,792 422,792Q430,792 439.5,792Q449,792 454.43,792Q459.86,792 459.86,792L459.86,792L529,792L666,792Q666,792 666,792Q666,792 666,792L666,792Z"/>
+    </group>
+</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable-night/reachability_education_ic_right_hand.xml b/libs/WindowManager/Shell/res/drawable-night/reachability_education_ic_right_hand.xml
new file mode 100644
index 0000000..d36df4b
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable-night/reachability_education_ic_right_hand.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="20dp"
+        android:height="20dp"
+        android:viewportWidth="960"
+        android:viewportHeight="960">
+    <path
+        android:fillColor="?android:attr/textColorSecondary"
+        android:pathData="M432.46,48Q522,48 585,110.92Q648,173.83 648,264Q648,314 627.5,358.5Q607,403 566,432L528,432L528,370Q551,349 563.5,321.5Q576,294 576,263.78Q576,204.39 534,162.2Q492,120 432,120Q372,120 330,162Q288,204 288,264.31Q288,295 300,323Q312,351 336,370L336,456Q280,430 248,378Q216,326 216,264Q216,173.83 278.97,110.92Q341.94,48 432.46,48ZM414,864Q399.53,864 386.77,859Q374,854 363,843L144,624L211,557Q225,543 243,538Q261,533 279,538L336,552L336,288Q337,248 364.57,220Q392.14,192 432.07,192Q472,192 500,219.84Q528,247.68 528,288L528,432L576,432Q576,432 576,432Q576,432 576,432L715,497Q744,511 758,538Q772,565 767,596L737,802Q732,828 711.76,846Q691.52,864 666,864L414,864ZM414,792L666,792L698,569Q698,569 698,569Q698,569 698,569L559,504L456,504L456,288Q456,278 449,271Q442,264 432,264Q422,264 415,271Q408,278 408,288L408,644L262,608L246,624L414,792ZM666,792L414,792L414,792L414,792L414,792L414,792Q414,792 422,792Q430,792 439.5,792Q449,792 454.43,792Q459.86,792 459.86,792L459.86,792L529,792L666,792Q666,792 666,792Q666,792 666,792L666,792Z"/>
+</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/reachability_education_ic_left_hand.xml b/libs/WindowManager/Shell/res/drawable/reachability_education_ic_left_hand.xml
index 029d838..05d243d 100644
--- a/libs/WindowManager/Shell/res/drawable/reachability_education_ic_left_hand.xml
+++ b/libs/WindowManager/Shell/res/drawable/reachability_education_ic_left_hand.xml
@@ -18,11 +18,10 @@
         android:width="20dp"
         android:height="20dp"
         android:viewportWidth="960"
-        android:viewportHeight="960"
-        android:tint="?attr/colorControlNormal">
+        android:viewportHeight="960">
     <group android:scaleX="-1" android:translateX="960">
         <path
-            android:fillColor="?android:attr/textColorSecondary"
+            android:fillColor="?android:attr/textColorSecondaryInverse"
             android:pathData="M432.46,48Q522,48 585,110.92Q648,173.83 648,264Q648,314 627.5,358.5Q607,403 566,432L528,432L528,370Q551,349 563.5,321.5Q576,294 576,263.78Q576,204.39 534,162.2Q492,120 432,120Q372,120 330,162Q288,204 288,264.31Q288,295 300,323Q312,351 336,370L336,456Q280,430 248,378Q216,326 216,264Q216,173.83 278.97,110.92Q341.94,48 432.46,48ZM414,864Q399.53,864 386.77,859Q374,854 363,843L144,624L211,557Q225,543 243,538Q261,533 279,538L336,552L336,288Q337,248 364.57,220Q392.14,192 432.07,192Q472,192 500,219.84Q528,247.68 528,288L528,432L576,432Q576,432 576,432Q576,432 576,432L715,497Q744,511 758,538Q772,565 767,596L737,802Q732,828 711.76,846Q691.52,864 666,864L414,864ZM414,792L666,792L698,569Q698,569 698,569Q698,569 698,569L559,504L456,504L456,288Q456,278 449,271Q442,264 432,264Q422,264 415,271Q408,278 408,288L408,644L262,608L246,624L414,792ZM666,792L414,792L414,792L414,792L414,792L414,792Q414,792 422,792Q430,792 439.5,792Q449,792 454.43,792Q459.86,792 459.86,792L459.86,792L529,792L666,792Q666,792 666,792Q666,792 666,792L666,792Z"/>
     </group>
 </vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/reachability_education_ic_right_hand.xml b/libs/WindowManager/Shell/res/drawable/reachability_education_ic_right_hand.xml
index 592f899..7bf243f 100644
--- a/libs/WindowManager/Shell/res/drawable/reachability_education_ic_right_hand.xml
+++ b/libs/WindowManager/Shell/res/drawable/reachability_education_ic_right_hand.xml
@@ -18,9 +18,8 @@
         android:width="20dp"
         android:height="20dp"
         android:viewportWidth="960"
-        android:viewportHeight="960"
-        android:tint="?attr/colorControlNormal">
+        android:viewportHeight="960">
     <path
-        android:fillColor="?android:attr/textColorSecondary"
+        android:fillColor="?android:attr/textColorSecondaryInverse"
         android:pathData="M432.46,48Q522,48 585,110.92Q648,173.83 648,264Q648,314 627.5,358.5Q607,403 566,432L528,432L528,370Q551,349 563.5,321.5Q576,294 576,263.78Q576,204.39 534,162.2Q492,120 432,120Q372,120 330,162Q288,204 288,264.31Q288,295 300,323Q312,351 336,370L336,456Q280,430 248,378Q216,326 216,264Q216,173.83 278.97,110.92Q341.94,48 432.46,48ZM414,864Q399.53,864 386.77,859Q374,854 363,843L144,624L211,557Q225,543 243,538Q261,533 279,538L336,552L336,288Q337,248 364.57,220Q392.14,192 432.07,192Q472,192 500,219.84Q528,247.68 528,288L528,432L576,432Q576,432 576,432Q576,432 576,432L715,497Q744,511 758,538Q772,565 767,596L737,802Q732,828 711.76,846Q691.52,864 666,864L414,864ZM414,792L666,792L698,569Q698,569 698,569Q698,569 698,569L559,504L456,504L456,288Q456,278 449,271Q442,264 432,264Q422,264 415,271Q408,278 408,288L408,644L262,608L246,624L414,792ZM666,792L414,792L414,792L414,792L414,792L414,792Q414,792 422,792Q430,792 439.5,792Q449,792 454.43,792Q459.86,792 459.86,792L459.86,792L529,792L666,792Q666,792 666,792Q666,792 666,792L666,792Z"/>
 </vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values-night/styles.xml b/libs/WindowManager/Shell/res/values-night/styles.xml
new file mode 100644
index 0000000..758c99d
--- /dev/null
+++ b/libs/WindowManager/Shell/res/values-night/styles.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <style name="ReachabilityEduHandLayout">
+        <item name="android:focusable">false</item>
+        <item name="android:focusableInTouchMode">false</item>
+        <item name="android:background">@android:color/transparent</item>
+        <item name="android:contentDescription">@string/restart_button_description</item>
+        <item name="android:visibility">invisible</item>
+        <item name="android:lineSpacingExtra">-1sp</item>
+        <item name="android:textSize">12sp</item>
+        <item name="android:textAlignment">center</item>
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:textAppearance">
+            @*android:style/TextAppearance.DeviceDefault.Body2
+        </item>
+    </style>
+
+</resources>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml
index ee80c472..2b38888 100644
--- a/libs/WindowManager/Shell/res/values/styles.xml
+++ b/libs/WindowManager/Shell/res/values/styles.xml
@@ -160,7 +160,7 @@
         <item name="android:lineSpacingExtra">-1sp</item>
         <item name="android:textSize">12sp</item>
         <item name="android:textAlignment">center</item>
-        <item name="android:textColor">?android:attr/textColorSecondary</item>
+        <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
         <item name="android:textAppearance">
             @*android:style/TextAppearance.DeviceDefault.Body2
         </item>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java
index f65c26a..d44b4d8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java
@@ -21,7 +21,6 @@
 import android.app.TaskInfo.CameraCompatControlState;
 import android.content.Context;
 import android.util.AttributeSet;
-import android.view.MotionEvent;
 import android.view.View;
 import android.widget.ImageButton;
 import android.widget.LinearLayout;
@@ -113,14 +112,6 @@
     }
 
     @Override
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
-        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            mWindowManager.relayout();
-        }
-        return super.onInterceptTouchEvent(ev);
-    }
-
-    @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index d4778fa..065806d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -204,14 +204,7 @@
                 : taskStableBounds.right - taskBounds.left - mLayout.getMeasuredWidth();
         final int positionY = taskStableBounds.bottom - taskBounds.top
                 - mLayout.getMeasuredHeight();
-        // To secure a proper visualisation, we hide the layout while updating the position of
-        // the {@link SurfaceControl} it belongs.
-        final int oldVisibility = mLayout.getVisibility();
-        if (oldVisibility == View.VISIBLE) {
-            mLayout.setVisibility(View.GONE);
-        }
         updateSurfacePosition(positionX, positionY);
-        mLayout.setVisibility(oldVisibility);
     }
 
     private void updateVisibilityOfViews() {
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 1d7e649..bbfeb90 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
@@ -52,7 +52,7 @@
  */
 public class PipAnimationController {
     static final float FRACTION_START = 0f;
-    private static final float FRACTION_END = 1f;
+    static final float FRACTION_END = 1f;
 
     public static final int ANIM_TYPE_BOUNDS = 0;
     public static final int ANIM_TYPE_ALPHA = 1;
@@ -718,7 +718,9 @@
                                 .round(tx, leash, sourceBounds, bounds)
                                 .shadow(tx, leash, shouldApplyShadowRadius());
                     }
-                    tx.apply();
+                    if (!handlePipTransaction(leash, tx, bounds, 1f /* alpha */)) {
+                        tx.apply();
+                    }
                 }
 
                 private Rect computeInsets(float fraction) {
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 363d675..8709eab 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
@@ -828,6 +828,7 @@
 
     private void onEndOfSwipePipToHomeTransition() {
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            mPipTransitionController.setEnterAnimationType(ANIM_TYPE_BOUNDS);
             return;
         }
 
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 6b8108a..db516c0 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
@@ -45,7 +45,6 @@
 import android.app.ActivityManager;
 import android.app.TaskInfo;
 import android.content.Context;
-import android.graphics.Matrix;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.IBinder;
@@ -109,6 +108,17 @@
     /** Whether the PIP window has fade out for fixed rotation. */
     private boolean mHasFadeOut;
 
+    /** Used for setting transform to a transaction from animator. */
+    private final PipAnimationController.PipTransactionHandler mTransactionConsumer =
+            new PipAnimationController.PipTransactionHandler() {
+                @Override
+                public boolean handlePipTransaction(SurfaceControl leash,
+                        SurfaceControl.Transaction tx, Rect destinationBounds, float alpha) {
+                    // Only set the operation to transaction but do not apply.
+                    return true;
+                }
+            };
+
     public PipTransition(Context context,
             @NonNull ShellInit shellInit,
             @NonNull ShellTaskOrganizer shellTaskOrganizer,
@@ -338,7 +348,7 @@
     @Override
     public void onFinishResize(TaskInfo taskInfo, Rect destinationBounds,
             @PipAnimationController.TransitionDirection int direction,
-            @Nullable SurfaceControl.Transaction tx) {
+            @NonNull SurfaceControl.Transaction tx) {
         final boolean enteringPip = isInPipDirection(direction);
         if (enteringPip) {
             mPipTransitionState.setTransitionState(ENTERED_PIP);
@@ -348,13 +358,15 @@
         // (likely a remote like launcher), so don't fire the finish-callback here -- wait until
         // the exit transition is merged.
         if ((mExitTransition == null || isAnimatingLocally()) && mFinishCallback != null) {
+            final SurfaceControl leash = mPipOrganizer.getSurfaceControl();
+            final boolean hasValidLeash = leash != null && leash.isValid();
             WindowContainerTransaction wct = null;
             if (isOutPipDirection(direction)) {
                 // Only need to reset surface properties. The server-side operations were already
                 // done at the start. But if it is running fixed rotation, there will be a seamless
                 // display transition later. So the last rotation transform needs to be kept to
                 // avoid flickering, and then the display transition will reset the transform.
-                if (tx != null && !mInFixedRotation) {
+                if (!mInFixedRotation && mFinishTransaction != null) {
                     mFinishTransaction.merge(tx);
                 }
             } else {
@@ -363,27 +375,36 @@
                     // If we are animating from fullscreen using a bounds animation, then reset the
                     // activity windowing mode, and set the task bounds to the final bounds
                     wct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
-                    wct.scheduleFinishEnterPip(taskInfo.token, destinationBounds);
                     wct.setBounds(taskInfo.token, destinationBounds);
                 } else {
                     wct.setBounds(taskInfo.token, null /* bounds */);
                 }
-                if (tx != null) {
-                    wct.setBoundsChangeTransaction(taskInfo.token, tx);
+                // Reset the scale with bounds change synchronously.
+                if (hasValidLeash) {
+                    mSurfaceTransactionHelper.crop(tx, leash, destinationBounds)
+                            .resetScale(tx, leash, destinationBounds)
+                            .round(tx, leash, true /* applyCornerRadius */);
                 }
+                wct.setBoundsChangeTransaction(taskInfo.token, tx);
             }
-            final SurfaceControl leash = mPipOrganizer.getSurfaceControl();
             final int displayRotation = taskInfo.getConfiguration().windowConfiguration
                     .getDisplayRotation();
             if (enteringPip && mInFixedRotation && mEndFixedRotation != displayRotation
-                    && leash != null && leash.isValid()) {
+                    && hasValidLeash) {
                 // Launcher may update the Shelf height during the animation, which will update the
                 // destination bounds. Because this is in fixed rotation, We need to make sure the
                 // finishTransaction is using the updated bounds in the display rotation.
+                final PipAnimationController.PipTransitionAnimator<?> animator =
+                        mPipAnimationController.getCurrentAnimator();
                 final Rect displayBounds = mPipDisplayLayoutState.getDisplayBounds();
                 final Rect finishBounds = new Rect(destinationBounds);
                 rotateBounds(finishBounds, displayBounds, mEndFixedRotation, displayRotation);
-                mSurfaceTransactionHelper.crop(mFinishTransaction, leash, finishBounds);
+                if (!finishBounds.equals(animator.getEndValue())) {
+                    ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                            "%s: Destination bounds were changed during animation", TAG);
+                    rotateBounds(finishBounds, displayBounds, mEndFixedRotation, displayRotation);
+                    mSurfaceTransactionHelper.crop(mFinishTransaction, leash, finishBounds);
+                }
             }
             mFinishTransaction = null;
             callFinishCallback(wct);
@@ -470,6 +491,7 @@
             @NonNull Transitions.TransitionFinishCallback finishCallback,
             @NonNull TaskInfo taskInfo, @Nullable TransitionInfo.Change pipTaskChange) {
         TransitionInfo.Change pipChange = pipTaskChange;
+        SurfaceControl activitySc = null;
         if (mCurrentPipTaskToken == null) {
             ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                     "%s: There is no existing PiP Task for TRANSIT_EXIT_PIP", TAG);
@@ -482,6 +504,7 @@
                 if (mCurrentPipTaskToken.equals(change.getLastParent())) {
                     // Find the activity that is exiting PiP.
                     pipChange = change;
+                    activitySc = change.getLeash();
                     break;
                 }
             }
@@ -498,17 +521,36 @@
         // case it may not be in the screen coordinate.
         // Reparent the pip leash to the root with max layer so that we can animate it outside of
         // parent crop, and make sure it is not covered by other windows.
-        final SurfaceControl pipLeash = pipChange.getLeash();
-        final int rootIdx = TransitionUtil.rootIndexFor(pipChange, info);
-        startTransaction.reparent(pipLeash, info.getRoot(rootIdx).getLeash());
+        final TransitionInfo.Root root = TransitionUtil.getRootFor(pipChange, info);
+        final SurfaceControl pipLeash;
+        if (activitySc != null) {
+            // Use a local leash to animate activity in case the activity has letterbox which may
+            // be broken by PiP animation, e.g. always end at 0,0 in parent and unable to include
+            // letterbox area in crop bounds.
+            final SurfaceControl activitySurface = pipChange.getLeash();
+            pipLeash = new SurfaceControl.Builder()
+                    .setName(activitySc + "_pip-leash")
+                    .setContainerLayer()
+                    .setHidden(false)
+                    .setParent(root.getLeash())
+                    .build();
+            startTransaction.reparent(activitySurface, pipLeash);
+            // Put the activity at local position with offset in case it is letterboxed.
+            final Point activityOffset = pipChange.getEndRelOffset();
+            startTransaction.setPosition(activitySc, activityOffset.x, activityOffset.y);
+        } else {
+            pipLeash = pipChange.getLeash();
+            startTransaction.reparent(pipLeash, root.getLeash());
+        }
         startTransaction.setLayer(pipLeash, Integer.MAX_VALUE);
         // Note: because of this, the bounds to animate should be translated to the root coordinate.
-        final Point offset = info.getRoot(rootIdx).getOffset();
+        final Point offset = root.getOffset();
         final Rect currentBounds = mPipBoundsState.getBounds();
         currentBounds.offset(-offset.x, -offset.y);
         startTransaction.setPosition(pipLeash, currentBounds.left, currentBounds.top);
 
         final WindowContainerToken pipTaskToken = pipChange.getContainer();
+        final boolean useLocalLeash = activitySc != null;
         final boolean toFullscreen = pipChange.getEndAbsBounds().equals(
                 mPipBoundsState.getDisplayBounds());
         mFinishCallback = (wct, wctCB) -> {
@@ -518,6 +560,14 @@
                 wct.setBounds(pipTaskToken, null);
                 mPipOrganizer.applyWindowingModeChangeOnExit(wct, TRANSITION_DIRECTION_LEAVE_PIP);
             }
+            if (useLocalLeash) {
+                if (mPipAnimationController.isAnimating()) {
+                    mPipAnimationController.getCurrentAnimator().end();
+                }
+                // Make sure the animator don't use the released leash, e.g. mergeAnimation.
+                mPipAnimationController.resetAnimatorState();
+                finishTransaction.remove(pipLeash);
+            }
             finishCallback.onTransitionFinished(wct, wctCB);
         };
         mFinishTransaction = finishTransaction;
@@ -545,7 +595,8 @@
         // Set the initial frame as scaling the end to the start.
         final Rect destinationBounds = new Rect(pipChange.getEndAbsBounds());
         destinationBounds.offset(-offset.x, -offset.y);
-        startTransaction.setWindowCrop(pipLeash, destinationBounds);
+        startTransaction.setWindowCrop(pipLeash, destinationBounds.width(),
+                destinationBounds.height());
         mSurfaceTransactionHelper.scale(startTransaction, pipLeash, destinationBounds,
                 currentBounds);
         startTransaction.apply();
@@ -635,9 +686,11 @@
     private void startExpandAnimation(final TaskInfo taskInfo, final SurfaceControl leash,
             final Rect baseBounds, final Rect startBounds, final Rect endBounds,
             final int rotationDelta) {
+        final Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
+                taskInfo.pictureInPictureParams, endBounds);
         final PipAnimationController.PipTransitionAnimator animator =
                 mPipAnimationController.getAnimator(taskInfo, leash, baseBounds, startBounds,
-                        endBounds, null /* sourceHintRect */, TRANSITION_DIRECTION_LEAVE_PIP,
+                        endBounds, sourceHintRect, TRANSITION_DIRECTION_LEAVE_PIP,
                         0 /* startingAngle */, rotationDelta);
         animator.setTransitionDirection(TRANSITION_DIRECTION_LEAVE_PIP)
                 .setPipAnimationCallback(mPipAnimationCallback)
@@ -770,10 +823,6 @@
             computeEnterPipRotatedBounds(rotationDelta, startRotation, endRotation, taskInfo,
                     destinationBounds, sourceHintRect);
         }
-        // Set corner radius for entering pip.
-        mSurfaceTransactionHelper
-                .crop(finishTransaction, leash, destinationBounds)
-                .round(finishTransaction, leash, true /* applyCornerRadius */);
         if (!mPipOrganizer.shouldAttachMenuEarly()) {
             mTransitions.getMainExecutor().executeDelayed(
                     () -> mPipMenuController.attach(leash), 0);
@@ -782,41 +831,11 @@
         if (taskInfo.pictureInPictureParams != null
                 && taskInfo.pictureInPictureParams.isAutoEnterEnabled()
                 && mPipTransitionState.getInSwipePipToHomeTransition()) {
-            final SurfaceControl swipePipToHomeOverlay = mPipOrganizer.mSwipePipToHomeOverlay;
-            startTransaction.setMatrix(leash, Matrix.IDENTITY_MATRIX, new float[9])
-                    .setPosition(leash, destinationBounds.left, destinationBounds.top)
-                    .setWindowCrop(leash, destinationBounds.width(), destinationBounds.height());
-            if (swipePipToHomeOverlay != null) {
-                // Launcher fade in the overlay on top of the fullscreen Task. It is possible we
-                // reparent the PIP activity to a new PIP task (in case there are other activities
-                // in the original Task), so we should also reparent the overlay to the PIP task.
-                startTransaction.reparent(swipePipToHomeOverlay, leash)
-                        .setLayer(swipePipToHomeOverlay, Integer.MAX_VALUE);
-                mPipOrganizer.mSwipePipToHomeOverlay = null;
-            }
-            startTransaction.apply();
-            if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) {
-                // For fixed rotation, set the destination bounds to the new rotation coordinates
-                // at the end.
-                destinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds());
-            }
-            mPipBoundsState.setBounds(destinationBounds);
-            onFinishResize(taskInfo, destinationBounds, TRANSITION_DIRECTION_TO_PIP, null /* tx */);
-            sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP);
-            if (swipePipToHomeOverlay != null) {
-                mPipOrganizer.fadeOutAndRemoveOverlay(swipePipToHomeOverlay,
-                        null /* callback */, false /* withStartDelay */);
-            }
-            mPipTransitionState.setInSwipePipToHomeTransition(false);
+            handleSwipePipToHomeTransition(startTransaction, finishTransaction, leash,
+                    sourceHintRect, destinationBounds, rotationDelta, taskInfo);
             return;
         }
 
-        if (rotationDelta != Surface.ROTATION_0) {
-            Matrix tmpTransform = new Matrix();
-            tmpTransform.postRotate(rotationDelta);
-            startTransaction.setMatrix(leash, tmpTransform, new float[9]);
-        }
-
         final int enterAnimationType = mEnterAnimationType;
         if (enterAnimationType == ANIM_TYPE_ALPHA) {
             startTransaction.setAlpha(leash, 0f);
@@ -850,11 +869,13 @@
         } else if (enterAnimationType == ANIM_TYPE_ALPHA) {
             animator = mPipAnimationController.getAnimator(taskInfo, leash, destinationBounds,
                     0f, 1f);
+            mSurfaceTransactionHelper
+                    .crop(finishTransaction, leash, destinationBounds)
+                    .round(finishTransaction, leash, true /* applyCornerRadius */);
         } else {
             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) {
@@ -863,7 +884,15 @@
             // ComputeRotatedBounds has changed the DisplayLayout without affecting the animation.
             animator.setDestinationBounds(mPipBoundsAlgorithm.getEntryDestinationBounds());
         }
-        animator.start();
+        // Keep the last appearance when finishing the transition. The transform will be reset when
+        // setting bounds.
+        animator.setPipTransactionHandler(mTransactionConsumer).applySurfaceControlTransaction(
+                leash, finishTransaction, PipAnimationController.FRACTION_END);
+        // Remove the workaround after fixing ClosePipBySwipingDownTest that detects the shadow
+        // as unexpected visible.
+        finishTransaction.setShadowRadius(leash, 0);
+        // Start to animate enter PiP.
+        animator.setPipTransactionHandler(mPipOrganizer.getPipTransactionHandler()).start();
     }
 
     /** Computes destination bounds in old rotation and updates source hint rect if available. */
@@ -885,6 +914,51 @@
         }
     }
 
+    private void handleSwipePipToHomeTransition(
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull SurfaceControl leash, @Nullable Rect sourceHintRect,
+            @NonNull Rect destinationBounds, int rotationDelta,
+            @NonNull ActivityManager.RunningTaskInfo pipTaskInfo) {
+        final SurfaceControl swipePipToHomeOverlay = mPipOrganizer.mSwipePipToHomeOverlay;
+        if (swipePipToHomeOverlay != null) {
+            // Launcher fade in the overlay on top of the fullscreen Task. It is possible we
+            // reparent the PIP activity to a new PIP task (in case there are other activities
+            // in the original Task), so we should also reparent the overlay to the PIP task.
+            startTransaction.reparent(swipePipToHomeOverlay, leash)
+                    .setLayer(swipePipToHomeOverlay, Integer.MAX_VALUE);
+            mPipOrganizer.mSwipePipToHomeOverlay = null;
+        }
+
+        Rect sourceBounds = pipTaskInfo.configuration.windowConfiguration.getBounds();
+        if (!Transitions.SHELL_TRANSITIONS_ROTATION && rotationDelta % 2 == 1) {
+            // PipController#startSwipePipToHome has updated the display layout to new rotation,
+            // so flip the source bounds to match the same orientation.
+            sourceBounds = new Rect(0, 0, sourceBounds.height(), sourceBounds.width());
+        }
+        final PipAnimationController.PipTransitionAnimator animator =
+                mPipAnimationController.getAnimator(pipTaskInfo, leash, sourceBounds, sourceBounds,
+                        destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
+                        0 /* startingAngle */, 0 /* rotationDelta */)
+                        .setPipTransactionHandler(mTransactionConsumer)
+                        .setTransitionDirection(TRANSITION_DIRECTION_TO_PIP);
+        // The start state is the end state for swipe-auto-pip.
+        startTransaction.merge(finishTransaction);
+        animator.applySurfaceControlTransaction(leash, startTransaction,
+                PipAnimationController.FRACTION_END);
+        startTransaction.apply();
+
+        mPipBoundsState.setBounds(destinationBounds);
+        final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+        onFinishResize(pipTaskInfo, destinationBounds, TRANSITION_DIRECTION_TO_PIP, tx);
+        sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP);
+        if (swipePipToHomeOverlay != null) {
+            mPipOrganizer.fadeOutAndRemoveOverlay(swipePipToHomeOverlay,
+                    null /* callback */, false /* withStartDelay */);
+        }
+        mPipTransitionState.setInSwipePipToHomeTransition(false);
+    }
+
     private void startExitToSplitAnimation(@NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction startTransaction,
             @NonNull SurfaceControl.Transaction finishTransaction,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index af52350..c654c1b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -337,6 +337,11 @@
         return isTaskInSplitScreen(taskId) && isSplitScreenVisible();
     }
 
+    /** Check whether the task is the single-top root or the root of one of the stages. */
+    public boolean isTaskRootOrStageRoot(int taskId) {
+        return mStageCoordinator.isRootOrStageRoot(taskId);
+    }
+
     public @SplitPosition int getSplitPosition(int taskId) {
         return mStageCoordinator.getSplitPosition(taskId);
     }
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 b8373f3..749549d 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
@@ -377,6 +377,13 @@
         return STAGE_TYPE_UNDEFINED;
     }
 
+    boolean isRootOrStageRoot(int taskId) {
+        if (mRootTaskInfo != null && mRootTaskInfo.taskId == taskId) {
+            return true;
+        }
+        return mMainStage.isRootTaskId(taskId) || mSideStage.isRootTaskId(taskId);
+    }
+
     boolean moveToStage(ActivityManager.RunningTaskInfo task, @SplitPosition int stagePosition,
             WindowContainerTransaction wct) {
         prepareEnterSplitScreen(wct, task, stagePosition);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 18b09b0..e2e9270 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -286,6 +286,10 @@
         }
     }
 
+    boolean isRootTaskId(int taskId) {
+        return mRootTaskInfo != null && mRootTaskInfo.taskId == taskId;
+    }
+
     void onResizing(Rect newBounds, Rect sideBounds, SurfaceControl.Transaction t, int offsetX,
             int offsetY, boolean immediately) {
         if (mSplitDecorManager != null && mRootTaskInfo != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
index 94190c7..d27933e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
@@ -453,7 +453,7 @@
             return;
         }
 
-        finishTransaction.reparent(mTaskLeash, null).apply();
+        finishTransaction.reparent(mTaskLeash, null);
 
         if (mListener != null) {
             final int taskId = mTaskInfo.taskId;
@@ -490,13 +490,11 @@
         if (mSurfaceCreated) {
             // Surface is ready, so just reparent the task to this surface control
             startTransaction.reparent(mTaskLeash, mSurfaceControl)
-                    .show(mTaskLeash)
-                    .apply();
+                    .show(mTaskLeash);
             // Also reparent on finishTransaction since the finishTransaction will reparent back
             // to its "original" parent by default.
             finishTransaction.reparent(mTaskLeash, mSurfaceControl)
-                    .setPosition(mTaskLeash, 0, 0)
-                    .apply();
+                    .setPosition(mTaskLeash, 0, 0);
             mTaskViewTransitions.updateBoundsState(this, mTaskViewBase.getCurrentBoundsOnScreen());
             mTaskViewTransitions.updateVisibilityState(this, true /* visible */);
             wct.setBounds(mTaskToken, mTaskViewBase.getCurrentBoundsOnScreen());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
index 689f9e1..fe2faaf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
@@ -363,7 +363,8 @@
                     continue;
                 }
                 startTransaction.reparent(chg.getLeash(), tv.getSurfaceControl());
-                finishTransaction.reparent(chg.getLeash(), tv.getSurfaceControl());
+                finishTransaction.reparent(chg.getLeash(), tv.getSurfaceControl())
+                    .setPosition(chg.getLeash(), 0, 0);
                 changesHandled++;
             }
         }
@@ -377,7 +378,6 @@
         }
         // No animation, just show it immediately.
         startTransaction.apply();
-        finishTransaction.apply();
         finishCallback.onTransitionFinished(wct, null /* wctCB */);
         startNextTransition();
         return true;
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 dfad8b9..21dca95 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
@@ -37,12 +37,8 @@
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
 import static android.view.WindowManager.TRANSIT_CHANGE;
-import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
-import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_RELAUNCH;
-import static android.view.WindowManager.TRANSIT_TO_BACK;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
 import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_OWNER_THUMBNAIL;
 import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL;
@@ -93,7 +89,6 @@
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 import android.view.WindowManager;
-import android.view.WindowManager.TransitionType;
 import android.view.animation.AlphaAnimation;
 import android.view.animation.Animation;
 import android.view.animation.Transformation;
@@ -346,9 +341,10 @@
                 continue;
             }
             final boolean isTask = change.getTaskInfo() != null;
+            final int mode = change.getMode();
             boolean isSeamlessDisplayChange = false;
 
-            if (change.getMode() == TRANSIT_CHANGE && (change.getFlags() & FLAG_IS_DISPLAY) != 0) {
+            if (mode == TRANSIT_CHANGE && change.hasFlags(FLAG_IS_DISPLAY)) {
                 if (info.getType() == TRANSIT_CHANGE) {
                     final int anim = getRotationAnimationHint(change, info, mDisplayController);
                     isSeamlessDisplayChange = anim == ROTATION_ANIMATION_SEAMLESS;
@@ -364,7 +360,7 @@
                 }
             }
 
-            if (change.getMode() == TRANSIT_CHANGE) {
+            if (mode == TRANSIT_CHANGE) {
                 // If task is child task, only set position in parent and update crop when needed.
                 if (isTask && change.getParent() != null
                         && info.getChange(change.getParent()).getTaskInfo() != null) {
@@ -413,8 +409,7 @@
 
             // Hide the invisible surface directly without animating it if there is a display
             // rotation animation playing.
-            if (isDisplayRotationAnimationStarted && TransitionUtil.isClosingType(
-                    change.getMode())) {
+            if (isDisplayRotationAnimationStarted && TransitionUtil.isClosingType(mode)) {
                 startTransaction.hide(change.getLeash());
                 continue;
             }
@@ -430,13 +425,9 @@
             Animation a = loadAnimation(info, change, wallpaperTransit, isDreamTransition);
             if (a != null) {
                 if (isTask) {
-                    final @TransitionType int type = info.getType();
-                    final boolean isOpenOrCloseTransition = type == TRANSIT_OPEN
-                            || type == TRANSIT_CLOSE
-                            || type == TRANSIT_TO_FRONT
-                            || type == TRANSIT_TO_BACK;
                     final boolean isTranslucent = (change.getFlags() & FLAG_TRANSLUCENT) != 0;
-                    if (isOpenOrCloseTransition && !isTranslucent
+                    if (!isTranslucent && TransitionUtil.isOpenOrCloseMode(mode)
+                            && TransitionUtil.isOpenOrCloseMode(info.getType())
                             && wallpaperTransit == WALLPAPER_TRANSITION_NONE) {
                         // Use the overview background as the background for the animation
                         final Context uiContext = ActivityThread.currentActivityThread()
@@ -461,7 +452,7 @@
                         backgroundColorForTransition);
 
                 if (!isTask && a.hasExtension()) {
-                    if (!TransitionUtil.isOpeningType(change.getMode())) {
+                    if (!TransitionUtil.isOpeningType(mode)) {
                         // Can screenshot now (before startTransaction is applied)
                         edgeExtendWindow(change, a, startTransaction, finishTransaction);
                     } else {
@@ -472,7 +463,7 @@
                     }
                 }
 
-                final Rect clipRect = TransitionUtil.isClosingType(change.getMode())
+                final Rect clipRect = TransitionUtil.isClosingType(mode)
                         ? new Rect(mRotator.getEndBoundsInStartRotation(change))
                         : new Rect(change.getEndAbsBounds());
                 clipRect.offsetTo(0, 0);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index fda943d7..ab27c55 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -501,7 +501,9 @@
      */
     private static void setupAnimHierarchy(@NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
-        boolean isOpening = isOpeningType(info.getType());
+        final int type = info.getType();
+        final boolean isOpening = isOpeningType(type);
+        final boolean isClosing = isClosingType(type);
         for (int i = 0; i < info.getRootCount(); ++i) {
             t.show(info.getRoot(i).getLeash());
         }
@@ -554,7 +556,13 @@
                     layer = zSplitLine + numChanges - i;
                 }
             } else { // CHANGE or other
-                layer = zSplitLine + numChanges - i;
+                if (isClosing) {
+                    // Put below CLOSE mode.
+                    layer = zSplitLine - i;
+                } else {
+                    // Put above CLOSE mode.
+                    layer = zSplitLine + numChanges - i;
+                }
             }
             t.setLayer(leash, layer);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java
index 96657af..9d4e6b4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java
@@ -20,6 +20,7 @@
 import static android.graphics.Color.green;
 import static android.graphics.Color.red;
 
+import android.annotation.ColorRes;
 import android.annotation.NonNull;
 import android.content.Context;
 import android.view.SurfaceControl;
@@ -33,10 +34,14 @@
 
     private static final int BACKGROUND_LAYER_Z_INDEX = -1;
     private final float[] mBackgroundColor;
+    private final float[] mSplitScreenBackgroundColor;
+    private float[] mBackgroundColorSet;
     private SurfaceControl mBackgroundLayer;
+    private boolean mSplitScreenVisible = false;
 
     public UnfoldBackgroundController(@NonNull Context context) {
-        mBackgroundColor = getBackgroundColor(context);
+        mBackgroundColor = getRGBColorFromId(context, R.color.unfold_background);
+        mSplitScreenBackgroundColor = getRGBColorFromId(context, R.color.split_divider_background);
     }
 
     /**
@@ -44,7 +49,14 @@
      * @param transaction where we should add the background if it is not added
      */
     public void ensureBackground(@NonNull SurfaceControl.Transaction transaction) {
-        if (mBackgroundLayer != null) return;
+        float[] expectedColor = getCurrentBackgroundColor();
+        if (mBackgroundLayer != null) {
+            if (mBackgroundColorSet != expectedColor) {
+                transaction.setColor(mBackgroundLayer, expectedColor);
+                mBackgroundColorSet = expectedColor;
+            }
+            return;
+        }
 
         SurfaceControl.Builder colorLayerBuilder = new SurfaceControl.Builder()
                 .setName("app-unfold-background")
@@ -53,9 +65,10 @@
         mBackgroundLayer = colorLayerBuilder.build();
 
         transaction
-                .setColor(mBackgroundLayer, mBackgroundColor)
+                .setColor(mBackgroundLayer, expectedColor)
                 .show(mBackgroundLayer)
                 .setLayer(mBackgroundLayer, BACKGROUND_LAYER_Z_INDEX);
+        mBackgroundColorSet = expectedColor;
     }
 
     /**
@@ -70,8 +83,25 @@
         mBackgroundLayer = null;
     }
 
-    private float[] getBackgroundColor(Context context) {
-        int colorInt = context.getResources().getColor(R.color.unfold_background);
+    /**
+     * Expected to be called whenever split screen visibility changes.
+     *
+     * @param visible True when split screen is visible
+     */
+    public void onSplitVisibilityChanged(boolean visible) {
+        mSplitScreenVisible = visible;
+    }
+
+    private float[] getCurrentBackgroundColor() {
+        if (mSplitScreenVisible) {
+            return mSplitScreenBackgroundColor;
+        } else {
+            return mBackgroundColor;
+        }
+    }
+
+    private float[] getRGBColorFromId(Context context, @ColorRes int id) {
+        int colorInt = context.getResources().getColor(id);
         return new float[]{
                 (float) red(colorInt) / 255.0F,
                 (float) green(colorInt) / 255.0F,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
index 2f0c964..123bf3b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
@@ -292,6 +292,11 @@
                 .setCornerRadius(context.mLeash, 0.0F);
     }
 
+    @Override
+    public void onSplitVisibilityChanged(boolean visible) {
+        mUnfoldBackgroundController.onSplitVisibilityChanged(visible);
+    }
+
     private class AnimationContext {
         final SurfaceControl mLeash;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
index 7d05c0e..402b0ce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/TransitionUtil.java
@@ -68,6 +68,12 @@
         return type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK;
     }
 
+    /** Returns {@code true} if the transition is opening or closing mode. */
+    public static boolean isOpenOrCloseMode(@TransitionInfo.TransitionMode int mode) {
+        return mode == TRANSIT_OPEN || mode == TRANSIT_CLOSE
+                || mode == TRANSIT_TO_FRONT || mode == TRANSIT_TO_BACK;
+    }
+
     /** Returns {@code true} if the transition has a display change. */
     public static boolean hasDisplayChange(@NonNull TransitionInfo info) {
         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
@@ -311,7 +317,7 @@
 
     private static RemoteAnimationTarget getDividerTarget(TransitionInfo.Change change,
             SurfaceControl leash) {
-        return new RemoteAnimationTarget(-1 /* taskId */, -1 /* mode */,
+        return new RemoteAnimationTarget(-1 /* taskId */, newModeToLegacyMode(change.getMode()),
                 leash, false /* isTranslucent */, null /* clipRect */,
                 null /* contentInsets */, Integer.MAX_VALUE /* prefixOrderIndex */,
                 new android.graphics.Point(0, 0) /* position */, change.getStartAbsBounds(),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 2bb3cce..39fb793 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -22,7 +22,6 @@
 
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
-import android.graphics.Rect;
 import android.os.Handler;
 import android.os.IBinder;
 import android.util.SparseArray;
@@ -186,8 +185,9 @@
                         mSyncQueue);
         mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
 
-        final DragPositioningCallback dragPositioningCallback = createDragPositioningCallback(
-                windowDecoration, taskInfo);
+        final DragPositioningCallback dragPositioningCallback =
+                new FluidResizeTaskPositioner(mTaskOrganizer, windowDecoration, mDisplayController,
+                        null /* disallowedAreaForEndBounds */);
         final CaptionTouchEventListener touchEventListener =
                 new CaptionTouchEventListener(taskInfo, dragPositioningCallback);
         windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
@@ -198,17 +198,6 @@
         setupCaptionColor(taskInfo, windowDecoration);
     }
 
-    private FluidResizeTaskPositioner createDragPositioningCallback(
-            CaptionWindowDecoration windowDecoration, RunningTaskInfo taskInfo) {
-        final int screenWidth = mDisplayController.getDisplayLayout(taskInfo.displayId).width();
-        final int statusBarHeight = mDisplayController.getDisplayLayout(taskInfo.displayId)
-                .stableInsets().top;
-        final Rect disallowedAreaForEndBounds = new Rect(0, 0, screenWidth,
-                statusBarHeight);
-        return new FluidResizeTaskPositioner(mTaskOrganizer, windowDecoration,
-                    mDisplayController, disallowedAreaForEndBounds);
-    }
-
     private class CaptionTouchEventListener implements
             View.OnClickListener, View.OnTouchListener, DragDetector.MotionEventHandler {
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 0b82184..54babce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -760,6 +760,10 @@
 
     private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
         if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
+        if (mSplitScreenController.isPresent()
+                && mSplitScreenController.get().isTaskRootOrStageRoot(taskInfo.taskId)) {
+            return false;
+        }
         return DesktopModeStatus.isProto2Enabled()
                 && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
                 && mDisplayController.getDisplayContext(taskInfo.displayId)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
index 9bcb77f..9f79d21 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
@@ -21,6 +21,8 @@
 import android.view.SurfaceControl;
 import android.window.WindowContainerTransaction;
 
+import androidx.annotation.Nullable;
+
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
 
@@ -42,24 +44,24 @@
     private final Rect mRepositionTaskBounds = new Rect();
     // If a task move (not resize) finishes in this region, the positioner will not attempt to
     // finalize the bounds there using WCT#setBounds
-    private final Rect mDisallowedAreaForEndBounds = new Rect();
+    private final Rect mDisallowedAreaForEndBounds;
     private boolean mHasDragResized;
     private int mCtrlType;
 
     FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
-            DisplayController displayController, Rect disallowedAreaForEndBounds) {
+            DisplayController displayController, @Nullable Rect disallowedAreaForEndBounds) {
         this(taskOrganizer, windowDecoration, displayController, disallowedAreaForEndBounds,
                 dragStartListener -> {}, SurfaceControl.Transaction::new);
     }
 
     FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
-            DisplayController displayController, Rect disallowedAreaForEndBounds,
+            DisplayController displayController, @Nullable Rect disallowedAreaForEndBounds,
             DragPositioningCallbackUtility.DragStartListener dragStartListener,
             Supplier<SurfaceControl.Transaction> supplier) {
         mTaskOrganizer = taskOrganizer;
         mWindowDecoration = windowDecoration;
         mDisplayController = displayController;
-        mDisallowedAreaForEndBounds.set(disallowedAreaForEndBounds);
+        mDisallowedAreaForEndBounds = new Rect(disallowedAreaForEndBounds);
         mDragStartListener = dragStartListener;
         mTransactionSupplier = supplier;
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
index 55781f1b..78c3cbd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -28,7 +28,6 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -363,14 +362,14 @@
         mWindowManager.updateVisibility(/* canShow= */ false);
 
         verify(mWindowManager, never()).createLayout(anyBoolean());
-        verify(mLayout, atLeastOnce()).setVisibility(View.GONE);
+        verify(mLayout).setVisibility(View.GONE);
 
         // Show button.
         doReturn(View.GONE).when(mLayout).getVisibility();
         mWindowManager.updateVisibility(/* canShow= */ true);
 
         verify(mWindowManager, never()).createLayout(anyBoolean());
-        verify(mLayout, atLeastOnce()).setVisibility(View.VISIBLE);
+        verify(mLayout).setVisibility(View.VISIBLE);
     }
 
     @Test
diff --git a/media/aidl/android/media/soundtrigger_middleware/PhraseRecognitionEventSys.aidl b/media/aidl/android/media/soundtrigger_middleware/PhraseRecognitionEventSys.aidl
index d9d16ec..58aed4a 100644
--- a/media/aidl/android/media/soundtrigger_middleware/PhraseRecognitionEventSys.aidl
+++ b/media/aidl/android/media/soundtrigger_middleware/PhraseRecognitionEventSys.aidl
@@ -21,6 +21,7 @@
  * Wrapper to android.media.soundtrigger.RecognitionEvent providing additional fields used by the
  * framework.
  */
+@JavaDerive(equals = true, toString = true)
 parcelable PhraseRecognitionEventSys {
 
     PhraseRecognitionEvent phraseRecognitionEvent;
diff --git a/media/aidl/android/media/soundtrigger_middleware/RecognitionEventSys.aidl b/media/aidl/android/media/soundtrigger_middleware/RecognitionEventSys.aidl
index 20ec8c2..dc22f68 100644
--- a/media/aidl/android/media/soundtrigger_middleware/RecognitionEventSys.aidl
+++ b/media/aidl/android/media/soundtrigger_middleware/RecognitionEventSys.aidl
@@ -21,6 +21,7 @@
  * Wrapper to android.media.soundtrigger.RecognitionEvent providing additional fields used by the
  * framework.
  */
+@JavaDerive(equals = true, toString = true)
 parcelable RecognitionEventSys {
 
     RecognitionEvent recognitionEvent;
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 0f5280e..c85ffd4 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -38,6 +38,7 @@
 import com.android.credentialmanager.getflow.GetCredentialUiState
 import com.android.credentialmanager.getflow.findAutoSelectEntry
 import com.android.credentialmanager.common.ProviderActivityState
+import com.android.credentialmanager.createflow.isFlowAutoSelectable
 
 /**
  * Client for interacting with Credential Manager. Also holds data inputs from it.
@@ -49,6 +50,7 @@
     private val context: Context,
     intent: Intent,
     userConfigRepo: UserConfigRepo,
+    isNewActivity: Boolean,
 ) {
     val requestInfo: RequestInfo?
     private val providerEnabledList: List<ProviderData>
@@ -112,20 +114,30 @@
                 val providerDisableListUiState = getCreateProviderDisableListInitialUiState()
                 val requestDisplayInfoUiState =
                     getCreateRequestDisplayInfoInitialUiState(originName)!!
+                val createCredentialUiState = CreateFlowUtils.toCreateCredentialUiState(
+                    enabledProviders = providerEnableListUiState,
+                    disabledProviders = providerDisableListUiState,
+                    defaultProviderIdPreferredByApp =
+                    requestDisplayInfoUiState.appPreferredDefaultProviderId,
+                    defaultProviderIdsSetByUser =
+                    requestDisplayInfoUiState.userSetDefaultProviderIds,
+                    requestDisplayInfo = requestDisplayInfoUiState,
+                    isOnPasskeyIntroStateAlready = false,
+                    isPasskeyFirstUse = isPasskeyFirstUse,
+                )!!
+                val isFlowAutoSelectable = isFlowAutoSelectable(createCredentialUiState)
                 UiState(
-                    createCredentialUiState = CreateFlowUtils.toCreateCredentialUiState(
-                        enabledProviders = providerEnableListUiState,
-                        disabledProviders = providerDisableListUiState,
-                        defaultProviderIdPreferredByApp =
-                        requestDisplayInfoUiState.appPreferredDefaultProviderId,
-                        defaultProviderIdsSetByUser =
-                        requestDisplayInfoUiState.userSetDefaultProviderIds,
-                        requestDisplayInfo = requestDisplayInfoUiState,
-                        isOnPasskeyIntroStateAlready = false,
-                        isPasskeyFirstUse = isPasskeyFirstUse,
-                    )!!,
+                    createCredentialUiState = createCredentialUiState,
                     getCredentialUiState = null,
-                    cancelRequestState = cancelUiRequestState
+                    cancelRequestState = cancelUiRequestState,
+                    isInitialRender = isNewActivity,
+                    isAutoSelectFlow = isFlowAutoSelectable,
+                    providerActivityState =
+                    if (isFlowAutoSelectable) ProviderActivityState.READY_TO_LAUNCH
+                    else ProviderActivityState.NOT_APPLICABLE,
+                    selectedEntry =
+                    if (isFlowAutoSelectable) createCredentialUiState.activeEntry?.activeEntryInfo
+                    else null,
                 )
             }
             RequestInfo.TYPE_GET -> {
@@ -140,7 +152,8 @@
                     if (autoSelectEntry == null) ProviderActivityState.NOT_APPLICABLE
                     else ProviderActivityState.READY_TO_LAUNCH,
                     isAutoSelectFlow = autoSelectEntry != null,
-                    cancelRequestState = cancelUiRequestState
+                    cancelRequestState = cancelUiRequestState,
+                    isInitialRender = isNewActivity,
                 )
             }
             else -> {
@@ -149,6 +162,7 @@
                         createCredentialUiState = null,
                         getCredentialUiState = null,
                         cancelRequestState = cancelUiRequestState,
+                        isInitialRender = isNewActivity,
                     )
                 } else {
                     throw IllegalStateException("Unrecognized request type: ${requestInfo?.type}")
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index 54a8678d..c409ba6 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -62,7 +62,8 @@
                 return
             }
             val userConfigRepo = UserConfigRepo(this)
-            val credManRepo = CredentialManagerRepo(this, intent, userConfigRepo)
+            val credManRepo = CredentialManagerRepo(
+                this, intent, userConfigRepo, isNewActivity = true)
 
             val backPressedCallback = object : OnBackPressedCallback(
                 true // default to enabled
@@ -103,7 +104,8 @@
                 }
             } else {
                 val userConfigRepo = UserConfigRepo(this)
-                val credManRepo = CredentialManagerRepo(this, intent, userConfigRepo)
+                val credManRepo = CredentialManagerRepo(
+                    this, intent, userConfigRepo, isNewActivity = false)
                 viewModel.onNewCredentialManagerRepo(credManRepo)
             }
         } catch (e: Exception) {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 081490e..cf962d1 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -34,6 +34,7 @@
 import com.android.credentialmanager.common.ProviderActivityResult
 import com.android.credentialmanager.common.ProviderActivityState
 import com.android.credentialmanager.createflow.ActiveEntry
+import com.android.credentialmanager.createflow.isFlowAutoSelectable
 import com.android.credentialmanager.createflow.CreateCredentialUiState
 import com.android.credentialmanager.createflow.CreateScreenState
 import com.android.credentialmanager.getflow.GetCredentialUiState
@@ -53,6 +54,7 @@
     // launched immediately, and canceling it will cancel the whole UI flow.
     val isAutoSelectFlow: Boolean = false,
     val cancelRequestState: CancelUiRequestState?,
+    val isInitialRender: Boolean,
 )
 
 data class CancelUiRequestState(
@@ -82,6 +84,10 @@
         uiState = uiState.copy(dialogState = DialogState.COMPLETE)
     }
 
+    fun onInitialRenderComplete() {
+        uiState = uiState.copy(isInitialRender = false)
+    }
+
     fun onCancellationUiRequested(appDisplayName: String?) {
         uiState = uiState.copy(cancelRequestState = CancelUiRequestState(appDisplayName))
     }
@@ -96,7 +102,7 @@
 
     fun onNewCredentialManagerRepo(credManRepo: CredentialManagerRepo) {
         this.credManRepo = credManRepo
-        uiState = credManRepo.initState()
+        uiState = credManRepo.initState().copy(isInitialRender = false)
 
         if (this.credManRepo.requestInfo?.token != credManRepo.requestInfo?.token) {
             this.uiMetrics.resetInstanceId()
@@ -114,7 +120,12 @@
             uiState = uiState.copy(providerActivityState = ProviderActivityState.PENDING)
             val intentSenderRequest = IntentSenderRequest.Builder(entry.pendingIntent)
                 .setFillInIntent(entry.fillInIntent).build()
-            launcher.launch(intentSenderRequest)
+            try {
+                launcher.launch(intentSenderRequest)
+            } catch (e: Exception) {
+                Log.w(Constants.LOG_TAG, "Failed to launch provider UI: $e")
+                onInternalError()
+            }
         } else {
             Log.d(Constants.LOG_TAG, "No provider UI to launch")
             onInternalError()
@@ -228,7 +239,8 @@
             getCredentialUiState = uiState.getCredentialUiState?.copy(
                 currentScreenState = GetScreenState.ALL_SIGN_IN_OPTIONS,
                 isNoAccount = isNoAccount,
-            )
+            ),
+            isInitialRender = true,
         )
     }
 
@@ -252,30 +264,39 @@
     /*****                     Create Flow Callbacks                      *****/
     /**************************************************************************/
     fun createFlowOnConfirmIntro() {
+        userConfigRepo.setIsPasskeyFirstUse(false)
         val prevUiState = uiState.createCredentialUiState
         if (prevUiState == null) {
             Log.d(Constants.LOG_TAG, "Encountered unexpected null create ui state")
             onInternalError()
             return
         }
-        val newUiState = CreateFlowUtils.toCreateCredentialUiState(
-            enabledProviders = prevUiState.enabledProviders,
-            disabledProviders = prevUiState.disabledProviders,
-            defaultProviderIdPreferredByApp =
-            prevUiState.requestDisplayInfo.appPreferredDefaultProviderId,
-            defaultProviderIdsSetByUser =
-            prevUiState.requestDisplayInfo.userSetDefaultProviderIds,
-            requestDisplayInfo = prevUiState.requestDisplayInfo,
+        val newScreenState = CreateFlowUtils.toCreateScreenState(
+            createOptionSize = prevUiState.sortedCreateOptionsPairs.size,
             isOnPasskeyIntroStateAlready = true,
-            isPasskeyFirstUse = userConfigRepo.getIsPasskeyFirstUse()
+            requestDisplayInfo = prevUiState.requestDisplayInfo,
+            remoteEntry = prevUiState.remoteEntry,
+            isPasskeyFirstUse = true,
         )
-        if (newUiState == null) {
-            Log.d(Constants.LOG_TAG, "Unable to update create ui state")
+        if (newScreenState == null) {
+            Log.d(Constants.LOG_TAG, "Unexpected: couldn't resolve new screen state")
             onInternalError()
             return
         }
-        uiState = uiState.copy(createCredentialUiState = newUiState)
-        userConfigRepo.setIsPasskeyFirstUse(false)
+        val newCreateCredentialUiState = prevUiState.copy(
+            currentScreenState = newScreenState,
+        )
+        val isFlowAutoSelectable = isFlowAutoSelectable(newCreateCredentialUiState)
+        uiState = uiState.copy(
+            createCredentialUiState = newCreateCredentialUiState,
+            isAutoSelectFlow = isFlowAutoSelectable,
+            providerActivityState =
+            if (isFlowAutoSelectable) ProviderActivityState.READY_TO_LAUNCH
+            else ProviderActivityState.NOT_APPLICABLE,
+            selectedEntry =
+            if (isFlowAutoSelectable) newCreateCredentialUiState.activeEntry?.activeEntryInfo
+            else null,
+        )
     }
 
     fun createFlowOnMoreOptionsSelectedOnCreationSelection() {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index b81339e8..70634f4 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -17,7 +17,6 @@
 package com.android.credentialmanager
 
 import android.app.slice.Slice
-import android.app.slice.SliceItem
 import android.content.ComponentName
 import android.content.Context
 import android.content.pm.PackageManager
@@ -201,7 +200,8 @@
                     ComponentName::class.java
                 )
             val preferTopBrandingContent: TopBrandingContent? =
-                if (preferUiBrandingComponentName == null) null
+                if (!requestInfo.hasPermissionToOverrideDefault() ||
+                    preferUiBrandingComponentName == null) null
                 else {
                     val (displayName, icon) = getServiceLabelAndIcon(
                         context.packageManager, preferUiBrandingComponentName.flattenToString())
@@ -335,12 +335,8 @@
                 val structuredAuthEntry =
                     AuthenticationAction.fromSlice(entry.slice) ?: return@forEach
 
-                // TODO: replace with official jetpack code.
-                val titleItem: SliceItem? = entry.slice.items.firstOrNull {
-                    it.hasHint(
-                        "androidx.credentials.provider.authenticationAction.SLICE_HINT_TITLE")
-                }
-                val title: String = titleItem?.text?.toString() ?: providerDisplayName
+                val title: String =
+                    structuredAuthEntry.title.toString().ifEmpty { providerDisplayName }
 
                 result.add(AuthenticationEntryInfo(
                     providerId = providerId,
@@ -487,6 +483,7 @@
                     createCredentialRequestJetpack.preferImmediatelyAvailableCredentials,
                     appPreferredDefaultProviderId = appPreferredDefaultProviderId,
                     userSetDefaultProviderIds = requestInfo.defaultProviderIds.toSet(),
+                    isAutoSelectRequest = createCredentialRequestJetpack.isAutoSelectAllowed,
                 )
                 is CreatePublicKeyCredentialRequest -> {
                     newRequestDisplayInfoFromPasskeyJson(
@@ -497,6 +494,7 @@
                         createCredentialRequestJetpack.preferImmediatelyAvailableCredentials,
                         appPreferredDefaultProviderId = appPreferredDefaultProviderId,
                         userSetDefaultProviderIds = requestInfo.defaultProviderIds.toSet(),
+                        isAutoSelectRequest = createCredentialRequestJetpack.isAutoSelectAllowed,
                     )
                 }
                 is CreateCustomCredentialRequest -> {
@@ -515,6 +513,7 @@
                         createCredentialRequestJetpack.preferImmediatelyAvailableCredentials,
                         appPreferredDefaultProviderId = appPreferredDefaultProviderId,
                         userSetDefaultProviderIds = requestInfo.defaultProviderIds.toSet(),
+                        isAutoSelectRequest = createCredentialRequestJetpack.isAutoSelectAllowed,
                     )
                 }
                 else -> null
@@ -602,7 +601,7 @@
             )
         }
 
-        private fun toCreateScreenState(
+        fun toCreateScreenState(
             createOptionSize: Int,
             isOnPasskeyIntroStateAlready: Boolean,
             requestDisplayInfo: RequestDisplayInfo,
@@ -661,8 +660,14 @@
                     passwordCount = createEntry.getPasswordCredentialCount(),
                     passkeyCount = createEntry.getPublicKeyCredentialCount(),
                     totalCredentialCount = createEntry.getTotalCredentialCount(),
-                    lastUsedTime = createEntry.lastUsedTime,
-                    footerDescription = createEntry.description?.toString()
+                    lastUsedTime = createEntry.lastUsedTime ?: Instant.MIN,
+                    footerDescription = createEntry.description?.toString(),
+                    // TODO(b/281065680): replace with official library constant once available
+                    allowAutoSelect =
+                    it.slice.items.firstOrNull {
+                        it.hasHint("androidx.credentials.provider.createEntry.SLICE_HINT_AUTO_" +
+                            "SELECT_ALLOWED")
+                    }?.text == "true",
                 ))
             }
             return result.sortedWith(
@@ -694,6 +699,7 @@
             preferImmediatelyAvailableCredentials: Boolean,
             appPreferredDefaultProviderId: String?,
             userSetDefaultProviderIds: Set<String>,
+            isAutoSelectRequest: Boolean
         ): RequestDisplayInfo? {
             val json = JSONObject(requestJson)
             var passkeyUsername = ""
@@ -716,6 +722,7 @@
                 preferImmediatelyAvailableCredentials,
                 appPreferredDefaultProviderId,
                 userSetDefaultProviderIds,
+                isAutoSelectRequest,
             )
         }
     }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
index 3399681..a5998faa 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
@@ -20,10 +20,6 @@
 import androidx.compose.foundation.layout.ColumnScope
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
-import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
@@ -40,16 +36,19 @@
 @Composable
 fun ModalBottomSheet(
     sheetContent: @Composable ColumnScope.() -> Unit,
-    onDismiss: () -> Unit
+    onDismiss: () -> Unit,
+    isInitialRender: Boolean,
+    onInitialRenderComplete: () -> Unit,
+    isAutoSelectFlow: Boolean,
 ) {
-    var isInitialRender by remember { mutableStateOf(true) }
     val scope = rememberCoroutineScope()
     val state = rememberModalBottomSheetState(
-        initialValue = ModalBottomSheetValue.Hidden,
+        initialValue = if (isAutoSelectFlow) ModalBottomSheetValue.Expanded
+        else ModalBottomSheetValue.Hidden,
         skipHalfExpanded = true
     )
     val sysUiController = rememberSystemUiController()
-    if (state.targetValue == ModalBottomSheetValue.Hidden) {
+    if (state.targetValue == ModalBottomSheetValue.Hidden || isAutoSelectFlow) {
         setTransparentSystemBarsColor(sysUiController)
     } else {
         setBottomSheetSystemBarsColor(sysUiController)
@@ -64,7 +63,7 @@
     LaunchedEffect(state.currentValue) {
         if (state.currentValue == ModalBottomSheetValue.Hidden) {
             if (isInitialRender) {
-                isInitialRender = false
+                onInitialRenderComplete()
                 scope.launch { state.show() }
             } else {
                 onDismiss()
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
index 2dba2ab..1c394ec 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
@@ -255,7 +255,7 @@
             Column(modifier = Modifier.wrapContentSize()
                 .padding(start = 16.dp, top = 16.dp, bottom = 16.dp)) {
                 SmallTitleText(entryHeadlineText)
-                if (entrySecondLineText != null) {
+                if (entrySecondLineText != null && entrySecondLineText.isNotEmpty()) {
                     BodySmallText(entrySecondLineText)
                 }
             }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index a258984..a3087cf 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -164,7 +164,10 @@
                 }
             }
         },
-        onDismiss = viewModel::onUserCancel
+        onDismiss = viewModel::onUserCancel,
+        isInitialRender = viewModel.uiState.isInitialRender,
+        isAutoSelectFlow = viewModel.uiState.isAutoSelectFlow,
+        onInitialRenderComplete = viewModel::onInitialRenderComplete,
     )
 }
 
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index fe1ce1b..cf7a943 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -34,6 +34,21 @@
   val foundCandidateFromUserDefaultProvider: Boolean,
 )
 
+internal fun isFlowAutoSelectable(
+    uiState: CreateCredentialUiState
+): Boolean {
+  return uiState.requestDisplayInfo.isAutoSelectRequest &&
+      // Even if the flow is auto selectable, still allow passkey intro screen to show once if
+      // applicable.
+      uiState.currentScreenState != CreateScreenState.PASSKEY_INTRO &&
+      uiState.currentScreenState != CreateScreenState.MORE_ABOUT_PASSKEYS_INTRO &&
+      uiState.remoteEntry == null &&
+      uiState.sortedCreateOptionsPairs.size == 1 &&
+      uiState.activeEntry?.activeEntryInfo?.let {
+        it is CreateOptionInfo && it.allowAutoSelect
+      } ?: false
+}
+
 internal fun hasContentToDisplay(state: CreateCredentialUiState): Boolean {
     return state.sortedCreateOptionsPairs.isNotEmpty() ||
         (!state.requestDisplayInfo.preferImmediatelyAvailableCredentials &&
@@ -72,8 +87,9 @@
     val passwordCount: Int?,
     val passkeyCount: Int?,
     val totalCredentialCount: Int?,
-    val lastUsedTime: Instant?,
+    val lastUsedTime: Instant,
     val footerDescription: String?,
+    val allowAutoSelect: Boolean,
 ) : BaseEntry(
     providerId,
     entryKey,
@@ -107,6 +123,8 @@
   val preferImmediatelyAvailableCredentials: Boolean,
   val appPreferredDefaultProviderId: String?,
   val userSetDefaultProviderIds: Set<String>,
+  // Whether the given CreateCredentialRequest allows auto select.
+  val isAutoSelectRequest: Boolean,
 )
 
 /**
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 31af7aa..934b0ae 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -148,6 +148,9 @@
                 }
             },
             onDismiss = viewModel::onUserCancel,
+            isInitialRender = viewModel.uiState.isInitialRender,
+            isAutoSelectFlow = viewModel.uiState.isAutoSelectFlow,
+            onInitialRenderComplete = viewModel::onInitialRenderComplete,
         )
     }
 }
@@ -558,7 +561,12 @@
     enforceOneLine: Boolean = false,
 ) {
     Entry(
-        onClick = { onEntrySelected(authenticationEntryInfo) },
+        onClick = if (authenticationEntryInfo.isUnlockedAndEmpty) {
+            {}
+        } // No-op
+        else {
+            { onEntrySelected(authenticationEntryInfo) }
+        },
         iconImageBitmap = authenticationEntryInfo.icon.toBitmap().asImageBitmap(),
         entryHeadlineText = authenticationEntryInfo.title,
         entrySecondLineText = stringResource(
diff --git a/packages/CtsShim/build/Android.bp b/packages/CtsShim/build/Android.bp
index af3e210..d778feb 100644
--- a/packages/CtsShim/build/Android.bp
+++ b/packages/CtsShim/build/Android.bp
@@ -43,6 +43,9 @@
 
     compile_multilib: "both",
     jni_libs: ["libshim_jni"],
+    // Explicitly uncompress native libs rather than letting the build system doing it and destroy the
+    // v2/v3 signature.
+    use_embedded_native_libs: true,
 
     uses_libs: ["android.test.runner"],
 
@@ -124,6 +127,9 @@
 
     compile_multilib: "both",
     jni_libs: ["libshim_jni"],
+    // Explicitly uncompress native libs rather than letting the build system doing it and destroy the
+    // v2/v3 signature.
+    use_embedded_native_libs: true,
 
     uses_libs: ["android.test.runner"],
 }
diff --git a/packages/SettingsLib/SpaPrivileged/AndroidManifest.xml b/packages/SettingsLib/SpaPrivileged/AndroidManifest.xml
index 5396de0..17c139b 100644
--- a/packages/SettingsLib/SpaPrivileged/AndroidManifest.xml
+++ b/packages/SettingsLib/SpaPrivileged/AndroidManifest.xml
@@ -18,6 +18,5 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.settingslib.spaprivileged">
     <uses-permission android:name="android.permission.MANAGE_USERS" />
-    <uses-permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS" />
 </manifest>
 
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt
index 23270c1..eb3d7dc 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt
@@ -55,6 +55,7 @@
     @Before
     fun setUp() {
         whenever(context.appOpsManager).thenReturn(appOpsManager)
+        whenever(context.packageManager).thenReturn(packageManager)
         doNothing().`when`(packageManager)
                 .updatePermissionFlags(anyString(), anyString(), anyInt(), anyInt(), any())
     }
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
index af59a55..c54f4f8 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
@@ -19,6 +19,7 @@
 import android.app.AppOpsManager
 import android.content.Context
 import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
 import androidx.compose.runtime.State
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.lifecycle.MutableLiveData
@@ -38,6 +39,10 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.doNothing
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.Spy
@@ -57,11 +62,16 @@
 
     @Mock private lateinit var appOpsManager: AppOpsManager
 
+    @Mock private lateinit var packageManager: PackageManager
+
     private lateinit var listModel: TestAppOpPermissionAppListModel
 
     @Before
     fun setUp() {
         whenever(context.appOpsManager).thenReturn(appOpsManager)
+        whenever(context.packageManager).thenReturn(packageManager)
+        doNothing().`when`(packageManager)
+                .updatePermissionFlags(anyString(), anyString(), anyInt(), anyInt(), any())
         listModel = TestAppOpPermissionAppListModel()
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 810545c..f12aa26 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -6,6 +6,7 @@
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothCsipSetCoordinator;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
@@ -174,14 +175,21 @@
                     resources, ((BitmapDrawable) pair.first).getBitmap()), pair.second);
         }
 
+        int hashCode;
+        if ((cachedDevice.getGroupId() != BluetoothCsipSetCoordinator.GROUP_ID_INVALID)) {
+            hashCode = new Integer(cachedDevice.getGroupId()).hashCode();
+        } else {
+            hashCode = cachedDevice.getAddress().hashCode();
+        }
+
         return new Pair<>(buildBtRainbowDrawable(context,
-                pair.first, cachedDevice.getAddress().hashCode()), pair.second);
+                pair.first, hashCode), pair.second);
     }
 
     /**
      * Build Bluetooth device icon with rainbow
      */
-    public static Drawable buildBtRainbowDrawable(Context context, Drawable drawable,
+    private static Drawable buildBtRainbowDrawable(Context context, Drawable drawable,
             int hashCode) {
         final Resources resources = context.getResources();
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index bf95ab9..1aa1741 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -1548,8 +1548,7 @@
             refresh();
         }
 
-        return new Pair<>(BluetoothUtils.buildBtRainbowDrawable(
-                        mContext, pair.first, getAddress().hashCode()), pair.second);
+        return BluetoothUtils.getBtRainbowDrawableWithDescription(mContext, this);
     }
 
     void releaseLruCache() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 6cf6825..82c6f11 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -542,13 +542,6 @@
                 //TODO(b/148765806): use correct device type once api is ready.
                 mediaDevice = new InfoMediaDevice(mContext, mRouterManager, route,
                         mPackageName, mPreferenceItemMap.get(route.getId()));
-                if (!TextUtils.isEmpty(mPackageName)
-                        && getRoutingSessionInfo().getSelectedRoutes().contains(route.getId())) {
-                    mediaDevice.setState(STATE_SELECTED);
-                    if (mCurrentConnectedDevice == null) {
-                        mCurrentConnectedDevice = mediaDevice;
-                    }
-                }
                 break;
             case TYPE_BUILTIN_SPEAKER:
             case TYPE_USB_DEVICE:
@@ -581,7 +574,13 @@
                 break;
 
         }
-
+        if (mediaDevice != null && !TextUtils.isEmpty(mPackageName)
+                && getRoutingSessionInfo().getSelectedRoutes().contains(route.getId())) {
+            mediaDevice.setState(STATE_SELECTED);
+            if (mCurrentConnectedDevice == null) {
+                mCurrentConnectedDevice = mediaDevice;
+            }
+        }
         if (mediaDevice != null) {
             mMediaDevices.add(mediaDevice);
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java
index ac9cdac..e034254 100644
--- a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java
+++ b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java
@@ -95,9 +95,6 @@
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
 
-        if (!isLaidOut()) {
-            return;
-        }
         if (mMaskBitmap == null) {
             mMaskBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
             mMaskCanvas = new Canvas(mMaskBitmap);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index 0969327..aa5f3df 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -25,6 +25,8 @@
 import static android.media.MediaRoute2ProviderService.REASON_NETWORK_ERROR;
 import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR;
 
+import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_SELECTED;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -1006,6 +1008,37 @@
     }
 
     @Test
+    public void addMediaDevice_deviceIncludedInSelectedDevices_shouldSetAsCurrentConnected() {
+        final MediaRoute2Info route2Info = mock(MediaRoute2Info.class);
+        final CachedBluetoothDeviceManager cachedBluetoothDeviceManager =
+                mock(CachedBluetoothDeviceManager.class);
+        final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
+        final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
+        final RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class);
+        routingSessionInfos.add(sessionInfo);
+
+        when(mRouterManager.getRoutingSessions(TEST_PACKAGE_NAME)).thenReturn(routingSessionInfos);
+        when(sessionInfo.getSelectedRoutes()).thenReturn(ImmutableList.of(TEST_ID));
+        when(route2Info.getType()).thenReturn(TYPE_BLUETOOTH_A2DP);
+        when(route2Info.getAddress()).thenReturn("00:00:00:00:00:00");
+        when(route2Info.getId()).thenReturn(TEST_ID);
+        when(mLocalBluetoothManager.getCachedDeviceManager())
+                .thenReturn(cachedBluetoothDeviceManager);
+        when(cachedBluetoothDeviceManager.findDevice(any(BluetoothDevice.class)))
+                .thenReturn(cachedDevice);
+        mInfoMediaManager.mRouterManager = mRouterManager;
+
+        mInfoMediaManager.mMediaDevices.clear();
+        mInfoMediaManager.addMediaDevice(route2Info);
+
+        MediaDevice device = mInfoMediaManager.mMediaDevices.get(0);
+
+        assertThat(device instanceof BluetoothMediaDevice).isTrue();
+        assertThat(device.getState()).isEqualTo(STATE_SELECTED);
+        assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(device);
+    }
+
+    @Test
     public void shouldDisableMediaOutput_infosIsEmpty_returnsTrue() {
         mShadowRouter2Manager.setTransferableRoutes(new ArrayList<>());
 
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 85623b2..753c860 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -183,6 +183,7 @@
         VALIDATORS.put(System.NOTIFICATION_LIGHT_PULSE, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.POINTER_LOCATION, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.SHOW_TOUCHES, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(System.SHOW_KEY_PRESSES, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.WINDOW_ORIENTATION_LISTENER_LOG, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.LOCKSCREEN_SOUNDS_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.LOCKSCREEN_DISABLED, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index d3a9e91..1fd84c7 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -2778,6 +2778,9 @@
                 Settings.System.SHOW_TOUCHES,
                 SystemSettingsProto.DevOptions.SHOW_TOUCHES);
         dumpSetting(s, p,
+                Settings.System.SHOW_KEY_PRESSES,
+                SystemSettingsProto.DevOptions.SHOW_KEY_PRESSES);
+        dumpSetting(s, p,
                 Settings.System.POINTER_LOCATION,
                 SystemSettingsProto.DevOptions.POINTER_LOCATION);
         dumpSetting(s, p,
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 2e49dd5..73123c2 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -77,7 +77,8 @@
                     Settings.System.SCREEN_BRIGHTNESS, // removed in P
                     Settings.System.SETUP_WIZARD_HAS_RUN, // Only used by SuW
                     Settings.System.SHOW_GTALK_SERVICE_STATUS, // candidate for backup?
-                    Settings.System.SHOW_TOUCHES, // bug?
+                    Settings.System.SHOW_TOUCHES,
+                    Settings.System.SHOW_KEY_PRESSES,
                     Settings.System.SIP_ADDRESS_ONLY, // value, not a setting
                     Settings.System.SIP_ALWAYS, // value, not a setting
                     Settings.System.SYSTEM_LOCALES, // bug?
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index a27f113..e2f8345 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -1098,5 +1098,16 @@
             android:exported="true"
             android:permission="android.permission.CUSTOMIZE_SYSTEM_UI"
             />
+
+        <!-- TODO(b/278897602): Disable EmojiCompatInitializer until threading issues are fixed.
+             https://developer.android.com/reference/androidx/emoji2/text/EmojiCompatInitializer -->
+        <provider
+            android:name="androidx.startup.InitializationProvider"
+            android:authorities="${applicationId}.androidx-startup"
+            android:exported="false"
+            tools:node="merge">
+            <meta-data android:name="androidx.emoji2.text.EmojiCompatInitializer"
+                tools:node="remove" />
+        </provider>
     </application>
 </manifest>
diff --git a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
index f050a1e..c85449d0 100644
--- a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
+++ b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
@@ -83,12 +83,5 @@
                 android:contentDescription="@string/data_connection_roaming"
                 android:visibility="gone" />
         </FrameLayout>
-        <ImageView
-            android:id="@+id/mobile_roaming_large"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:src="@drawable/stat_sys_roaming_large"
-            android:contentDescription="@string/data_connection_roaming"
-            android:visibility="gone" />
     </com.android.keyguard.AlphaOptimizedLinearLayout>
 </merge>
diff --git a/packages/SystemUI/res/layout/dream_overlay_container.xml b/packages/SystemUI/res/layout/dream_overlay_container.xml
index ae0a937..19fb874 100644
--- a/packages/SystemUI/res/layout/dream_overlay_container.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_container.xml
@@ -25,10 +25,6 @@
         android:id="@+id/dream_overlay_content"
         android:layout_width="match_parent"
         android:layout_height="0dp"
-        android:paddingTop="@dimen/dream_overlay_container_padding_top"
-        android:paddingEnd="@dimen/dream_overlay_container_padding_end"
-        android:paddingBottom="@dimen/dream_overlay_container_padding_bottom"
-        android:paddingStart="@dimen/dream_overlay_container_padding_start"
         android:clipToPadding="false"
         android:clipChildren="false"
         app:layout_constraintTop_toBottomOf="@id/dream_overlay_status_bar"
diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml
index 8a66f50..13425c9 100644
--- a/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml
+++ b/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml
@@ -34,6 +34,7 @@
                  android:layout_height="wrap_content">
         <EditText
             android:id="@+id/keyboard_shortcuts_search"
+            android:layout_gravity="center_vertical|start"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_marginTop="24dp"
@@ -54,58 +55,62 @@
 
         <ImageView
             android:id="@+id/keyboard_shortcuts_search_cancel"
+            android:layout_gravity="center_vertical|end"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_gravity="end"
-            android:layout_marginTop="24dp"
-            android:layout_marginBottom="24dp"
             android:layout_marginEnd="49dp"
             android:padding="16dp"
             android:contentDescription="@string/keyboard_shortcut_clear_text"
             android:src="@drawable/ic_shortcutlist_search_button_cancel" />
     </FrameLayout>
 
-    <LinearLayout
+    <HorizontalScrollView
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:orientation="horizontal">
-        <View
-            android:layout_width="0dp"
-            android:layout_height="0dp"
-            android:layout_marginStart="37dp"/>
-
-        <Button
-            android:id="@+id/shortcut_system"
-            android:layout_width="120dp"
+        android:scrollbars="none">
+        <LinearLayout
+            android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_marginStart="12dp"
-            style="@style/ShortCutButton"
-            android:text="@string/keyboard_shortcut_search_category_system"/>
+            android:gravity="center_vertical"
+            android:orientation="horizontal">
+            <View
+                android:layout_width="0dp"
+                android:layout_height="0dp"
+                android:layout_marginStart="37dp"/>
 
-        <Button
-            android:id="@+id/shortcut_input"
-            android:layout_width="120dp"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="12dp"
-            style="@style/ShortCutButton"
-            android:text="@string/keyboard_shortcut_search_category_input"/>
+            <Button
+                android:id="@+id/shortcut_system"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="12dp"
+                style="@style/ShortCutButton"
+                android:text="@string/keyboard_shortcut_search_category_system"/>
 
-        <Button
-            android:id="@+id/shortcut_open_apps"
-            android:layout_width="120dp"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="12dp"
-            style="@style/ShortCutButton"
-            android:text="@string/keyboard_shortcut_search_category_open_apps"/>
+            <Button
+                android:id="@+id/shortcut_input"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="12dp"
+                style="@style/ShortCutButton"
+                android:text="@string/keyboard_shortcut_search_category_input"/>
 
-        <Button
-            android:id="@+id/shortcut_specific_app"
-            android:layout_width="120dp"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="12dp"
-            style="@style/ShortCutButton"
-            android:text="@string/keyboard_shortcut_search_category_current_app"/>
-    </LinearLayout>
+            <Button
+                android:id="@+id/shortcut_open_apps"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="12dp"
+                style="@style/ShortCutButton"
+                android:text="@string/keyboard_shortcut_search_category_open_apps"/>
+
+            <Button
+                android:id="@+id/shortcut_specific_app"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="12dp"
+                style="@style/ShortCutButton"
+                android:text="@string/keyboard_shortcut_search_category_current_app"/>
+        </LinearLayout>
+    </HorizontalScrollView>
 
     <TextView
         android:id="@+id/shortcut_search_no_result"
diff --git a/packages/SystemUI/res/layout/mobile_signal_group.xml b/packages/SystemUI/res/layout/mobile_signal_group.xml
index 5552020..bfd079b 100644
--- a/packages/SystemUI/res/layout/mobile_signal_group.xml
+++ b/packages/SystemUI/res/layout/mobile_signal_group.xml
@@ -77,11 +77,4 @@
             android:contentDescription="@string/data_connection_roaming"
             android:visibility="gone" />
     </FrameLayout>
-    <ImageView
-        android:id="@+id/mobile_roaming_large"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:src="@drawable/stat_sys_roaming_large"
-        android:contentDescription="@string/data_connection_roaming"
-        android:visibility="gone" />
 </com.android.keyguard.AlphaOptimizedLinearLayout>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 1252695..26d6875 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -892,5 +892,5 @@
 
     <!-- Time (in ms) to delay the bouncer views from showing when passive auth may be used for
     device entry. -->
-    <integer name="primary_bouncer_passive_auth_delay">250</integer>
+    <integer name="primary_bouncer_passive_auth_delay">500</integer>
 </resources>
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
index b153785..1f75e81 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
@@ -16,6 +16,11 @@
 
 package com.android.keyguard;
 
+import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_ACTIVE_DATA_SUB_CHANGED;
+import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_ON_SIM_STATE_CHANGED;
+import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_ON_TELEPHONY_CAPABLE;
+import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_REFRESH_CARRIER_INFO;
+
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -32,6 +37,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.keyguard.logging.CarrierTextManagerLogger;
 import com.android.settingslib.WirelessUtils;
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Background;
@@ -40,6 +46,7 @@
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository;
 import com.android.systemui.telephony.TelephonyListenerManager;
 
+import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.Executor;
@@ -68,6 +75,7 @@
     private final AtomicBoolean mNetworkSupported = new AtomicBoolean();
     @VisibleForTesting
     protected KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private final CarrierTextManagerLogger mLogger;
     private final WifiRepository mWifiRepository;
     private final boolean[] mSimErrorState;
     private final int mSimSlotsNumber;
@@ -97,19 +105,13 @@
     protected final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
         @Override
         public void onRefreshCarrierInfo() {
-            if (DEBUG) {
-                Log.d(TAG, "onRefreshCarrierInfo(), mTelephonyCapable: "
-                        + Boolean.toString(mTelephonyCapable));
-            }
+            mLogger.logUpdateCarrierTextForReason(REASON_REFRESH_CARRIER_INFO);
             updateCarrierText();
         }
 
         @Override
         public void onTelephonyCapable(boolean capable) {
-            if (DEBUG) {
-                Log.d(TAG, "onTelephonyCapable() mTelephonyCapable: "
-                        + Boolean.toString(capable));
-            }
+            mLogger.logUpdateCarrierTextForReason(REASON_ON_TELEPHONY_CAPABLE);
             mTelephonyCapable = capable;
             updateCarrierText();
         }
@@ -121,7 +123,7 @@
                 return;
             }
 
-            if (DEBUG) Log.d(TAG, "onSimStateChanged: " + getStatusForIccState(simState));
+            mLogger.logUpdateCarrierTextForReason(REASON_ON_SIM_STATE_CHANGED);
             if (getStatusForIccState(simState) == CarrierTextManager.StatusMode.SimIoError) {
                 mSimErrorState[slotId] = true;
                 updateCarrierText();
@@ -137,6 +139,7 @@
         @Override
         public void onActiveDataSubscriptionIdChanged(int subId) {
             if (mNetworkSupported.get() && mCarrierTextCallback != null) {
+                mLogger.logUpdateCarrierTextForReason(REASON_ACTIVE_DATA_SUB_CHANGED);
                 updateCarrierText();
             }
         }
@@ -175,7 +178,9 @@
             WakefulnessLifecycle wakefulnessLifecycle,
             @Main Executor mainExecutor,
             @Background Executor bgExecutor,
-            KeyguardUpdateMonitor keyguardUpdateMonitor) {
+            KeyguardUpdateMonitor keyguardUpdateMonitor,
+            CarrierTextManagerLogger logger) {
+
         mContext = context;
         mIsEmergencyCallCapable = telephonyManager.isVoiceCapable();
 
@@ -191,6 +196,7 @@
         mMainExecutor = mainExecutor;
         mBgExecutor = bgExecutor;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mLogger = logger;
         mBgExecutor.execute(() -> {
             boolean supported = mContext.getPackageManager()
                     .hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
@@ -315,7 +321,7 @@
             subOrderBySlot[i] = -1;
         }
         final CharSequence[] carrierNames = new CharSequence[numSubs];
-        if (DEBUG) Log.d(TAG, "updateCarrierText(): " + numSubs);
+        mLogger.logUpdate(numSubs);
 
         for (int i = 0; i < numSubs; i++) {
             int subId = subs.get(i).getSubscriptionId();
@@ -325,9 +331,7 @@
             int simState = mKeyguardUpdateMonitor.getSimState(subId);
             CharSequence carrierName = subs.get(i).getCarrierName();
             CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName);
-            if (DEBUG) {
-                Log.d(TAG, "Handling (subId=" + subId + "): " + simState + " " + carrierName);
-            }
+            mLogger.logUpdateLoopStart(subId, simState, String.valueOf(carrierName));
             if (carrierTextForSimState != null) {
                 allSimsMissing = false;
                 carrierNames[i] = carrierTextForSimState;
@@ -340,9 +344,7 @@
                     // Wi-Fi is disassociated or disabled
                     if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
                             || mWifiRepository.isWifiConnectedWithValidSsid()) {
-                        if (DEBUG) {
-                            Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss);
-                        }
+                        mLogger.logUpdateWfcCheck();
                         anySimReadyAndInService = true;
                     }
                 }
@@ -379,7 +381,7 @@
                     if (i.getBooleanExtra(TelephonyManager.EXTRA_SHOW_PLMN, false)) {
                         plmn = i.getStringExtra(TelephonyManager.EXTRA_PLMN);
                     }
-                    if (DEBUG) Log.d(TAG, "Getting plmn/spn sticky brdcst " + plmn + "/" + spn);
+                    mLogger.logUpdateFromStickyBroadcast(plmn, spn);
                     if (Objects.equals(plmn, spn)) {
                         text = plmn;
                     } else {
@@ -409,6 +411,7 @@
                 !allSimsMissing,
                 subsIds,
                 airplaneMode);
+        mLogger.logCallbackSentFromUpdate(info);
         postToCallback(info);
         Trace.endSection();
     }
@@ -645,6 +648,7 @@
         private final Executor mMainExecutor;
         private final Executor mBgExecutor;
         private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+        private final CarrierTextManagerLogger mLogger;
         private boolean mShowAirplaneMode;
         private boolean mShowMissingSim;
 
@@ -658,7 +662,8 @@
                 WakefulnessLifecycle wakefulnessLifecycle,
                 @Main Executor mainExecutor,
                 @Background Executor bgExecutor,
-                KeyguardUpdateMonitor keyguardUpdateMonitor) {
+                KeyguardUpdateMonitor keyguardUpdateMonitor,
+                CarrierTextManagerLogger logger) {
             mContext = context;
             mSeparator = resources.getString(
                     com.android.internal.R.string.kg_text_message_separator);
@@ -669,6 +674,7 @@
             mMainExecutor = mainExecutor;
             mBgExecutor = bgExecutor;
             mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+            mLogger = logger;
         }
 
         /** */
@@ -688,7 +694,7 @@
             return new CarrierTextManager(
                     mContext, mSeparator, mShowAirplaneMode, mShowMissingSim, mWifiRepository,
                     mTelephonyManager, mTelephonyListenerManager, mWakefulnessLifecycle,
-                    mMainExecutor, mBgExecutor, mKeyguardUpdateMonitor);
+                    mMainExecutor, mBgExecutor, mKeyguardUpdateMonitor, mLogger);
         }
     }
     /**
@@ -716,6 +722,17 @@
             this.subscriptionIds = subscriptionIds;
             this.airplaneMode = airplaneMode;
         }
+
+        @Override
+        public String toString() {
+            return "CarrierTextCallbackInfo{"
+                    + "carrierText=" + carrierText
+                    + ", listOfCarriers=" + Arrays.toString(listOfCarriers)
+                    + ", anySimReady=" + anySimReady
+                    + ", subscriptionIds=" + Arrays.toString(subscriptionIds)
+                    + ", airplaneMode=" + airplaneMode
+                    + '}';
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt
new file mode 100644
index 0000000..3dc787c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt
@@ -0,0 +1,135 @@
+/*
+ * 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.keyguard.logging
+
+import androidx.annotation.IntDef
+import com.android.keyguard.CarrierTextManager.CarrierTextCallbackInfo
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.CarrierTextManagerLog
+import javax.inject.Inject
+
+/** Logger adapter for [CarrierTextManager] to add detailed messages in a [LogBuffer] */
+@SysUISingleton
+class CarrierTextManagerLogger @Inject constructor(@CarrierTextManagerLog val buffer: LogBuffer) {
+    /**
+     * This method and the methods below trace the execution of CarrierTextManager.updateCarrierText
+     */
+    fun logUpdate(numSubs: Int) {
+        buffer.log(
+            TAG,
+            LogLevel.VERBOSE,
+            { int1 = numSubs },
+            { "updateCarrierText: numSubs=$int1" },
+        )
+    }
+
+    fun logUpdateLoopStart(sub: Int, simState: Int, carrierName: String) {
+        buffer.log(
+            TAG,
+            LogLevel.VERBOSE,
+            {
+                int1 = sub
+                int2 = simState
+                str1 = carrierName
+            },
+            { "┣ updateCarrierText: updating sub=$int1 simState=$int2 carrierName=$str1" },
+        )
+    }
+
+    fun logUpdateWfcCheck() {
+        buffer.log(
+            TAG,
+            LogLevel.VERBOSE,
+            {},
+            { "┣ updateCarrierText: found WFC state" },
+        )
+    }
+
+    fun logUpdateFromStickyBroadcast(plmn: String, spn: String) {
+        buffer.log(
+            TAG,
+            LogLevel.VERBOSE,
+            {
+                str1 = plmn
+                str2 = spn
+            },
+            { "┣ updateCarrierText: getting PLMN/SPN sticky brdcst. plmn=$str1, spn=$str1" },
+        )
+    }
+
+    /** De-structures the info object so that we don't have to generate new strings */
+    fun logCallbackSentFromUpdate(info: CarrierTextCallbackInfo) {
+        buffer.log(
+            TAG,
+            LogLevel.VERBOSE,
+            {
+                str1 = "${info.carrierText}"
+                bool1 = info.anySimReady
+                bool2 = info.airplaneMode
+            },
+            {
+                "â”— updateCarrierText: " +
+                    "result=(carrierText=$str1, anySimReady=$bool1, airplaneMode=$bool2)"
+            },
+        )
+    }
+
+    /**
+     * Used to log the starting point for _why_ the carrier text is updating. In order to keep us
+     * from holding on to too many objects, we'll just use simple ints for reasons here
+     */
+    fun logUpdateCarrierTextForReason(@CarrierTextRefreshReason reason: Int) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            { int1 = reason },
+            { "refreshing carrier info for reason: ${reason.reasonMessage()}" }
+        )
+    }
+
+    companion object {
+        const val REASON_REFRESH_CARRIER_INFO = 1
+        const val REASON_ON_TELEPHONY_CAPABLE = 2
+        const val REASON_ON_SIM_STATE_CHANGED = 3
+        const val REASON_ACTIVE_DATA_SUB_CHANGED = 4
+
+        @Retention(AnnotationRetention.SOURCE)
+        @IntDef(
+            value =
+                [
+                    REASON_REFRESH_CARRIER_INFO,
+                    REASON_ON_TELEPHONY_CAPABLE,
+                    REASON_ON_SIM_STATE_CHANGED,
+                    REASON_ACTIVE_DATA_SUB_CHANGED,
+                ]
+        )
+        annotation class CarrierTextRefreshReason
+
+        private fun @receiver:CarrierTextRefreshReason Int.reasonMessage() =
+            when (this) {
+                REASON_REFRESH_CARRIER_INFO -> "REFRESH_CARRIER_INFO"
+                REASON_ON_TELEPHONY_CAPABLE -> "ON_TELEPHONY_CAPABLE"
+                REASON_ON_SIM_STATE_CHANGED -> "SIM_STATE_CHANGED"
+                REASON_ACTIVE_DATA_SUB_CHANGED -> "ACTIVE_DATA_SUB_CHANGED"
+                else -> "unknown"
+            }
+    }
+}
+
+private const val TAG = "CarrierTextManagerLog"
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index aade71a..be5bb07 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -320,7 +320,6 @@
     @Inject @Main Lazy<Looper> mMainLooper;
     @Inject @Main Lazy<Handler> mMainHandler;
     @Inject @Named(TIME_TICK_HANDLER_NAME) Lazy<Handler> mTimeTickHandler;
-    @Nullable
     @Inject @Named(LEAK_REPORT_EMAIL_NAME) Lazy<String> mLeakReportEmail;
     @Inject @Main Lazy<Executor> mMainExecutor;
     @Inject @Background Lazy<Executor> mBackgroundExecutor;
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 8578845..70c39df 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -31,9 +31,6 @@
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
-import android.util.ArrayMap;
-import android.util.Dumpable;
-import android.util.DumpableContainer;
 import android.util.Log;
 import android.util.TimingsTraceLog;
 import android.view.SurfaceControl;
@@ -57,18 +54,12 @@
  * Application class for SystemUI.
  */
 public class SystemUIApplication extends Application implements
-        SystemUIAppComponentFactory.ContextInitializer, DumpableContainer {
+        SystemUIAppComponentFactory.ContextInitializer {
 
     public static final String TAG = "SystemUIService";
     private static final boolean DEBUG = false;
 
     private BootCompleteCacheImpl mBootCompleteCache;
-    private DumpManager mDumpManager;
-
-    /**
-     * Map of dumpables added externally.
-     */
-    private final ArrayMap<String, Dumpable> mDumpables = new ArrayMap<>();
 
     /**
      * Hold a reference on the stuff we start.
@@ -233,7 +224,7 @@
             }
         }
 
-        mDumpManager = mSysUIComponent.createDumpManager();
+        DumpManager dumpManager = mSysUIComponent.createDumpManager();
 
         Log.v(TAG, "Starting SystemUI services for user " +
                 Process.myUserHandle().getIdentifier() + ".");
@@ -267,7 +258,7 @@
                 notifyBootCompleted(mServices[i]);
             }
 
-            mDumpManager.registerDumpable(mServices[i].getClass().getName(), mServices[i]);
+            dumpManager.registerDumpable(mServices[i].getClass().getName(), mServices[i]);
         }
         mSysUIComponent.getInitController().executePostInitTasks();
         log.traceEnd();
@@ -342,36 +333,6 @@
         return startable;
     }
 
-    // TODO(b/217567642): add unit tests? There doesn't seem to be a SystemUiApplicationTest...
-    @Override
-    public boolean addDumpable(Dumpable dumpable) {
-        String name = dumpable.getDumpableName();
-        if (mDumpables.containsKey(name)) {
-            // This is normal because SystemUIApplication is an application context that is shared
-            // among multiple components
-            if (DEBUG) {
-                Log.d(TAG, "addDumpable(): ignoring " + dumpable + " as there is already a dumpable"
-                        + " with that name (" + name + "): " + mDumpables.get(name));
-            }
-            return false;
-        }
-        if (DEBUG) Log.d(TAG, "addDumpable(): adding '" + name + "' = " + dumpable);
-        mDumpables.put(name, dumpable);
-
-        // TODO(b/217567642): replace com.android.systemui.dump.Dumpable by
-        // com.android.util.Dumpable and get rid of the intermediate lambda
-        mDumpManager.registerDumpable(dumpable.getDumpableName(), dumpable::dump);
-        return true;
-    }
-
-    // TODO(b/217567642): implement
-    @Override
-    public boolean removeDumpable(Dumpable dumpable) {
-        Log.w(TAG, "removeDumpable(" + dumpable + "): not implemented");
-
-        return false;
-    }
-
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
         if (mServicesStarted) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
index b72801d..8edccf166 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
@@ -21,7 +21,7 @@
     fun enable(onPanelInteraction: Runnable) {
         if (action == null) {
             action = Action(onPanelInteraction)
-            shadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged)
+            shadeExpansionStateManager.addShadeExpansionListener(this::onPanelExpansionChanged)
         } else {
             Log.e(TAG, "Already enabled")
         }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
index c831663..096d941 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
@@ -29,6 +29,8 @@
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
 import com.android.systemui.biometrics.domain.interactor.LogContextInteractor
 import com.android.systemui.biometrics.domain.interactor.LogContextInteractorImpl
+import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractor
+import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractorImpl
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.util.concurrency.ThreadFactory
 import dagger.Binds
@@ -65,6 +67,11 @@
     @SysUISingleton
     fun bindsLogContextInteractor(impl: LogContextInteractorImpl): LogContextInteractor
 
+    @Binds
+    @SysUISingleton
+    fun providesSideFpsOverlayInteractor(impl: SideFpsOverlayInteractorImpl):
+            SideFpsOverlayInteractor
+
     companion object {
         /** Background [Executor] for HAL related operations. */
         @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
index 33fb36c..c43722f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
@@ -57,13 +57,8 @@
     /** The types of fingerprint sensor (rear, ultrasonic, optical, etc.). */
     val sensorType: StateFlow<FingerprintSensorType>
 
-    /** The primary sensor location relative to the default display. */
-    val sensorLocation: StateFlow<SensorLocationInternal>
-
-    // TODO(b/251476085): don't implement until we need it, but expose alternative locations as
-    // a map of display id -> location or similar.
     /** The sensor location relative to each physical display. */
-    // val sensorLocations<Map<String, SensorLocationInternal>>
+    val sensorLocations: StateFlow<Map<String, SensorLocationInternal>>
 }
 
 @SysUISingleton
@@ -104,15 +99,19 @@
         MutableStateFlow(FingerprintSensorType.UNKNOWN)
     override val sensorType = _sensorType.asStateFlow()
 
-    private val _sensorLocation: MutableStateFlow<SensorLocationInternal> =
-        MutableStateFlow(SensorLocationInternal.DEFAULT)
-    override val sensorLocation = _sensorLocation.asStateFlow()
+    private val _sensorLocations: MutableStateFlow<Map<String, SensorLocationInternal>> =
+        MutableStateFlow(mapOf("" to SensorLocationInternal.DEFAULT))
+    override val sensorLocations: StateFlow<Map<String, SensorLocationInternal>> =
+        _sensorLocations.asStateFlow()
 
     private fun setProperties(prop: FingerprintSensorPropertiesInternal) {
         _sensorId.value = prop.sensorId
         _strength.value = sensorStrengthIntToObject(prop.sensorStrength)
         _sensorType.value = sensorTypeIntToObject(prop.sensorType)
-        _sensorLocation.value = prop.location
+        _sensorLocations.value =
+            prop.allLocations.associateBy { sensorLocationInternal ->
+                sensorLocationInternal.displayId
+            }
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt
new file mode 100644
index 0000000..aa85e5f3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.biometrics.domain.interactor
+
+import android.hardware.biometrics.SensorLocationInternal
+import android.util.Log
+import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/** Business logic for SideFps overlay offsets. */
+interface SideFpsOverlayInteractor {
+
+    /** Get the corresponding offsets based on different displayId. */
+    fun getOverlayOffsets(displayId: String): SensorLocationInternal
+}
+
+@SysUISingleton
+class SideFpsOverlayInteractorImpl
+@Inject
+constructor(private val fingerprintPropertyRepository: FingerprintPropertyRepository) :
+    SideFpsOverlayInteractor {
+
+    override fun getOverlayOffsets(displayId: String): SensorLocationInternal {
+        val offsets = fingerprintPropertyRepository.sensorLocations.value
+        return if (offsets.containsKey(displayId)) {
+            offsets[displayId]!!
+        } else {
+            Log.w(TAG, "No location specified for display: $displayId")
+            offsets[""]!!
+        }
+    }
+
+    companion object {
+        private const val TAG = "SideFpsOverlayInteractorImpl"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/complication/ComplicationLayoutEngine.java b/packages/SystemUI/src/com/android/systemui/complication/ComplicationLayoutEngine.java
index e82564d..e1dd1a6 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/ComplicationLayoutEngine.java
+++ b/packages/SystemUI/src/com/android/systemui/complication/ComplicationLayoutEngine.java
@@ -16,9 +16,21 @@
 
 package com.android.systemui.complication;
 
+import static com.android.systemui.complication.ComplicationLayoutParams.DIRECTION_DOWN;
+import static com.android.systemui.complication.ComplicationLayoutParams.DIRECTION_END;
+import static com.android.systemui.complication.ComplicationLayoutParams.DIRECTION_START;
+import static com.android.systemui.complication.ComplicationLayoutParams.DIRECTION_UP;
+import static com.android.systemui.complication.ComplicationLayoutParams.POSITION_BOTTOM;
+import static com.android.systemui.complication.ComplicationLayoutParams.POSITION_END;
+import static com.android.systemui.complication.ComplicationLayoutParams.POSITION_START;
+import static com.android.systemui.complication.ComplicationLayoutParams.POSITION_TOP;
 import static com.android.systemui.complication.dagger.ComplicationHostViewModule.COMPLICATIONS_FADE_IN_DURATION;
 import static com.android.systemui.complication.dagger.ComplicationHostViewModule.COMPLICATIONS_FADE_OUT_DURATION;
-import static com.android.systemui.complication.dagger.ComplicationHostViewModule.COMPLICATION_MARGIN_DEFAULT;
+import static com.android.systemui.complication.dagger.ComplicationHostViewModule.COMPLICATION_DIRECTIONAL_SPACING_DEFAULT;
+import static com.android.systemui.complication.dagger.ComplicationHostViewModule.COMPLICATION_MARGIN_POSITION_BOTTOM;
+import static com.android.systemui.complication.dagger.ComplicationHostViewModule.COMPLICATION_MARGIN_POSITION_END;
+import static com.android.systemui.complication.dagger.ComplicationHostViewModule.COMPLICATION_MARGIN_POSITION_START;
+import static com.android.systemui.complication.dagger.ComplicationHostViewModule.COMPLICATION_MARGIN_POSITION_TOP;
 import static com.android.systemui.complication.dagger.ComplicationHostViewModule.SCOPED_COMPLICATIONS_LAYOUT;
 
 import android.util.Log;
@@ -29,6 +41,7 @@
 import androidx.constraintlayout.widget.Constraints;
 
 import com.android.systemui.R;
+import com.android.systemui.complication.ComplicationLayoutParams.Direction;
 import com.android.systemui.complication.ComplicationLayoutParams.Position;
 import com.android.systemui.complication.dagger.ComplicationModule;
 import com.android.systemui.statusbar.CrossFadeHelper;
@@ -55,6 +68,47 @@
     public static final String TAG = "ComplicationLayoutEng";
 
     /**
+     * Container for storing and operating on a tuple of margin values.
+     */
+    public static class Margins {
+        public final int start;
+        public final int top;
+        public final int end;
+        public final int bottom;
+
+        /**
+         * Default constructor with all margins set to 0.
+         */
+        public Margins() {
+            this(0, 0, 0, 0);
+        }
+
+        /**
+         * Cosntructor to specify margin in each direction.
+         * @param start start margin
+         * @param top top margin
+         * @param end end margin
+         * @param bottom bottom margin
+         */
+        public Margins(int start, int top, int end, int bottom) {
+            this.start = start;
+            this.top = top;
+            this.end = end;
+            this.bottom = bottom;
+        }
+
+        /**
+         * Creates a new {@link Margins} by adding the corresponding dimensions together.
+         */
+        public static Margins combine(Margins margins1, Margins margins2) {
+            return new Margins(margins1.start + margins2.start,
+                    margins1.top + margins2.top,
+                    margins1.end + margins2.end,
+                    margins1.bottom + margins2.bottom);
+        }
+    }
+
+    /**
      * {@link ViewEntry} is an internal container, capturing information necessary for working with
      * a particular {@link Complication} view.
      */
@@ -65,15 +119,13 @@
         private final Parent mParent;
         @Complication.Category
         private final int mCategory;
-        private final int mDefaultMargin;
 
         /**
          * Default constructor. {@link Parent} allows for the {@link ViewEntry}'s surrounding
          * view hierarchy to be accessed without traversing the entire view tree.
          */
         ViewEntry(View view, ComplicationLayoutParams layoutParams,
-                TouchInsetManager.TouchInsetSession touchSession, int category, Parent parent,
-                int defaultMargin) {
+                TouchInsetManager.TouchInsetSession touchSession, int category, Parent parent) {
             mView = view;
             // Views that are generated programmatically do not have a unique id assigned to them
             // at construction. A new id is assigned here to enable ConstraintLayout relative
@@ -84,7 +136,6 @@
             mTouchInsetSession = touchSession;
             mCategory = category;
             mParent = parent;
-            mDefaultMargin = defaultMargin;
 
             touchSession.addViewToTracking(mView);
         }
@@ -192,23 +243,8 @@
                         break;
                 }
 
-                if (!isRoot) {
-                    final int margin = mLayoutParams.getMargin(mDefaultMargin);
-                    switch(direction) {
-                        case ComplicationLayoutParams.DIRECTION_DOWN:
-                            params.setMargins(0, margin, 0, 0);
-                            break;
-                        case ComplicationLayoutParams.DIRECTION_UP:
-                            params.setMargins(0, 0, 0, margin);
-                            break;
-                        case ComplicationLayoutParams.DIRECTION_END:
-                            params.setMarginStart(margin);
-                            break;
-                        case ComplicationLayoutParams.DIRECTION_START:
-                            params.setMarginEnd(margin);
-                            break;
-                    }
-                }
+                final Margins margins = mParent.getMargins(this, isRoot);
+                params.setMarginsRelative(margins.start, margins.top, margins.end, margins.bottom);
             });
 
             if (mLayoutParams.constraintSpecified()) {
@@ -275,7 +311,6 @@
             private final ComplicationLayoutParams mLayoutParams;
             private final int mCategory;
             private Parent mParent;
-            private int mDefaultMargin;
 
             Builder(View view, TouchInsetManager.TouchInsetSession touchSession,
                     ComplicationLayoutParams lp, @Complication.Category int category) {
@@ -311,20 +346,10 @@
             }
 
             /**
-             * Sets the margin that will be applied in the direction the complication is laid out
-             * towards.
-             */
-            Builder setDefaultMargin(int margin) {
-                mDefaultMargin = margin;
-                return this;
-            }
-
-            /**
              * Builds and returns the resulting {@link ViewEntry}.
              */
             ViewEntry build() {
-                return new ViewEntry(mView, mLayoutParams, mTouchSession, mCategory, mParent,
-                        mDefaultMargin);
+                return new ViewEntry(mView, mLayoutParams, mTouchSession, mCategory, mParent);
             }
         }
 
@@ -336,6 +361,11 @@
              * Indicates the {@link ViewEntry} requests removal.
              */
             void removeEntry(ViewEntry entry);
+
+            /**
+             * Returns the margins to be applied to the entry
+             */
+            Margins getMargins(ViewEntry entry, boolean isRoot);
         }
     }
 
@@ -347,6 +377,15 @@
     private static class PositionGroup implements DirectionGroup.Parent {
         private final HashMap<Integer, DirectionGroup> mDirectionGroups = new HashMap<>();
 
+        private final HashMap<Integer, Margins> mDirectionalMargins;
+
+        private final int mDefaultDirectionalSpacing;
+
+        PositionGroup(int defaultDirectionalSpacing, HashMap<Integer, Margins> directionalMargins) {
+            mDefaultDirectionalSpacing = defaultDirectionalSpacing;
+            mDirectionalMargins = directionalMargins;
+        }
+
         /**
          * Invoked by the {@link PositionGroup} holder to introduce a {@link Complication} view to
          * this group. It is assumed that the caller has correctly identified this
@@ -370,6 +409,26 @@
             updateViews();
         }
 
+        @Override
+        public int getDefaultDirectionalSpacing() {
+            return mDefaultDirectionalSpacing;
+        }
+
+        @Override
+        public Margins getMargins(ViewEntry entry, boolean isRoot) {
+            if (isRoot) {
+                Margins cumulativeMargins = new Margins();
+
+                for (Margins margins : mDirectionalMargins.values()) {
+                    cumulativeMargins = Margins.combine(margins, cumulativeMargins);
+                }
+
+                return cumulativeMargins;
+            }
+
+            return mDirectionalMargins.get(entry.getLayoutParams().getDirection());
+        }
+
         private void updateViews() {
             ViewEntry head = null;
 
@@ -417,14 +476,22 @@
              * {@link DirectionGroup}.
              */
             void onEntriesChanged();
+
+            /**
+             * Returns the default spacing between elements.
+             */
+            int getDefaultDirectionalSpacing();
+
+            /**
+             * Returns the margins for the view entry.
+             */
+            Margins getMargins(ViewEntry entry, boolean isRoot);
         }
         private final ArrayList<ViewEntry> mViews = new ArrayList<>();
         private final Parent mParent;
 
         /**
-         * Creates a new {@link DirectionGroup} with the specified parent. Note that the
-         * {@link DirectionGroup} does not store its own direction. It is the responsibility of the
-         * {@link DirectionGroup.Parent} to maintain this association.
+         * Creates a new {@link DirectionGroup} with the specified parent.
          */
         DirectionGroup(Parent parent) {
             mParent = parent;
@@ -463,6 +530,33 @@
             mParent.onEntriesChanged();
         }
 
+        @Override
+        public Margins getMargins(ViewEntry entry, boolean isRoot) {
+            int directionalSpacing = entry.getLayoutParams().getDirectionalSpacing(
+                    mParent.getDefaultDirectionalSpacing());
+
+            Margins margins = new Margins();
+
+            if (!isRoot) {
+                switch (entry.getLayoutParams().getDirection()) {
+                    case ComplicationLayoutParams.DIRECTION_START:
+                        margins = new Margins(0, 0, directionalSpacing, 0);
+                        break;
+                    case ComplicationLayoutParams.DIRECTION_UP:
+                        margins = new Margins(0, 0, 0, directionalSpacing);
+                        break;
+                    case ComplicationLayoutParams.DIRECTION_END:
+                        margins = new Margins(directionalSpacing, 0, 0, 0);
+                        break;
+                    case ComplicationLayoutParams.DIRECTION_DOWN:
+                        margins = new Margins(0, directionalSpacing, 0, 0);
+                        break;
+                }
+            }
+
+            return Margins.combine(mParent.getMargins(entry, isRoot), margins);
+        }
+
         /**
          * Invoked by {@link Parent} to update the layout of all children {@link ViewEntry} with
          * the specified head. Note that the head might not be in this group and instead part of a
@@ -484,25 +578,70 @@
     }
 
     private final ConstraintLayout mLayout;
-    private final int mDefaultMargin;
+    private final int mDefaultDirectionalSpacing;
     private final HashMap<ComplicationId, ViewEntry> mEntries = new HashMap<>();
     private final HashMap<Integer, PositionGroup> mPositions = new HashMap<>();
     private final TouchInsetManager.TouchInsetSession mSession;
     private final int mFadeInDuration;
     private final int mFadeOutDuration;
+    private final HashMap<Integer, HashMap<Integer, Margins>> mPositionDirectionMarginMapping;
 
     /** */
     @Inject
     public ComplicationLayoutEngine(@Named(SCOPED_COMPLICATIONS_LAYOUT) ConstraintLayout layout,
-            @Named(COMPLICATION_MARGIN_DEFAULT) int defaultMargin,
+            @Named(COMPLICATION_DIRECTIONAL_SPACING_DEFAULT) int defaultDirectionalSpacing,
+            @Named(COMPLICATION_MARGIN_POSITION_START) int complicationMarginPositionStart,
+            @Named(COMPLICATION_MARGIN_POSITION_TOP) int complicationMarginPositionTop,
+            @Named(COMPLICATION_MARGIN_POSITION_END) int complicationMarginPositionEnd,
+            @Named(COMPLICATION_MARGIN_POSITION_BOTTOM) int complicationMarginPositionBottom,
             TouchInsetManager.TouchInsetSession session,
             @Named(COMPLICATIONS_FADE_IN_DURATION) int fadeInDuration,
             @Named(COMPLICATIONS_FADE_OUT_DURATION) int fadeOutDuration) {
         mLayout = layout;
-        mDefaultMargin = defaultMargin;
+        mDefaultDirectionalSpacing = defaultDirectionalSpacing;
         mSession = session;
         mFadeInDuration = fadeInDuration;
         mFadeOutDuration = fadeOutDuration;
+        mPositionDirectionMarginMapping = generatePositionDirectionalMarginsMapping(
+                complicationMarginPositionStart,
+                complicationMarginPositionTop,
+                complicationMarginPositionEnd,
+                complicationMarginPositionBottom);
+    }
+
+    private static HashMap<Integer, HashMap<Integer, Margins>>
+            generatePositionDirectionalMarginsMapping(int complicationMarginPositionStart,
+            int complicationMarginPositionTop,
+            int complicationMarginPositionEnd,
+            int complicationMarginPositionBottom) {
+        HashMap<Integer, HashMap<Integer, Margins>> mapping = new HashMap<>();
+
+        final Margins startMargins = new Margins(complicationMarginPositionStart, 0, 0, 0);
+        final Margins topMargins = new Margins(0, complicationMarginPositionTop, 0, 0);
+        final Margins endMargins = new Margins(0, 0, complicationMarginPositionEnd, 0);
+        final Margins bottomMargins = new Margins(0, 0, 0, complicationMarginPositionBottom);
+
+        addToMapping(mapping, POSITION_START | POSITION_TOP, DIRECTION_END, topMargins);
+        addToMapping(mapping, POSITION_START | POSITION_TOP, DIRECTION_DOWN, startMargins);
+
+        addToMapping(mapping, POSITION_START | POSITION_BOTTOM, DIRECTION_END, bottomMargins);
+        addToMapping(mapping, POSITION_START | POSITION_BOTTOM, DIRECTION_UP, startMargins);
+
+        addToMapping(mapping, POSITION_END | POSITION_TOP, DIRECTION_START, topMargins);
+        addToMapping(mapping, POSITION_END | POSITION_TOP, DIRECTION_DOWN, endMargins);
+
+        addToMapping(mapping, POSITION_END | POSITION_BOTTOM, DIRECTION_START, bottomMargins);
+        addToMapping(mapping, POSITION_END | POSITION_BOTTOM, DIRECTION_UP, endMargins);
+
+        return mapping;
+    }
+
+    private static void addToMapping(HashMap<Integer, HashMap<Integer, Margins>> mapping,
+            @Position int position, @Direction int direction, Margins margins) {
+        if (!mapping.containsKey(position)) {
+            mapping.put(position, new HashMap<>());
+        }
+        mapping.get(position).put(direction, margins);
     }
 
     @Override
@@ -537,13 +676,13 @@
             removeComplication(id);
         }
 
-        final ViewEntry.Builder entryBuilder = new ViewEntry.Builder(view, mSession, lp, category)
-                .setDefaultMargin(mDefaultMargin);
+        final ViewEntry.Builder entryBuilder = new ViewEntry.Builder(view, mSession, lp, category);
 
         // Add position group if doesn't already exist
         final int position = lp.getPosition();
         if (!mPositions.containsKey(position)) {
-            mPositions.put(position, new PositionGroup());
+            mPositions.put(position, new PositionGroup(mDefaultDirectionalSpacing,
+                    mPositionDirectionMarginMapping.get(lp.getPosition())));
         }
 
         // Insert entry into group
diff --git a/packages/SystemUI/src/com/android/systemui/complication/ComplicationLayoutParams.java b/packages/SystemUI/src/com/android/systemui/complication/ComplicationLayoutParams.java
index 71ba720..42b4efd 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/ComplicationLayoutParams.java
+++ b/packages/SystemUI/src/com/android/systemui/complication/ComplicationLayoutParams.java
@@ -51,7 +51,7 @@
     private static final int FIRST_POSITION = POSITION_TOP;
     private static final int LAST_POSITION = POSITION_END;
 
-    private static final int MARGIN_UNSPECIFIED = 0xFFFFFFFF;
+    private static final int DIRECTIONAL_SPACING_UNSPECIFIED = 0xFFFFFFFF;
     private static final int CONSTRAINT_UNSPECIFIED = 0xFFFFFFFF;
 
     @Retention(RetentionPolicy.SOURCE)
@@ -80,7 +80,7 @@
 
     private final int mWeight;
 
-    private final int mMargin;
+    private final int mDirectionalSpacing;
 
     private final int mConstraint;
 
@@ -113,7 +113,25 @@
      */
     public ComplicationLayoutParams(int width, int height, @Position int position,
             @Direction int direction, int weight) {
-        this(width, height, position, direction, weight, MARGIN_UNSPECIFIED, CONSTRAINT_UNSPECIFIED,
+        this(width, height, position, direction, weight, DIRECTIONAL_SPACING_UNSPECIFIED,
+                CONSTRAINT_UNSPECIFIED, false);
+    }
+
+    /**
+     * Constructs a {@link ComplicationLayoutParams}.
+     * @param width The width {@link android.view.View.MeasureSpec} for the view.
+     * @param height The height {@link android.view.View.MeasureSpec} for the view.
+     * @param position The place within the parent container where the view should be positioned.
+     * @param direction The direction the view should be laid out from either the parent container
+     *                  or preceding view.
+     * @param weight The weight that should be considered for this view when compared to other
+     *               views. This has an impact on the placement of the view but not the rendering of
+     *               the view.
+     * @param directionalSpacing The spacing to apply between complications.
+     */
+    public ComplicationLayoutParams(int width, int height, @Position int position,
+            @Direction int direction, int weight, int directionalSpacing) {
+        this(width, height, position, direction, weight, directionalSpacing, CONSTRAINT_UNSPECIFIED,
                 false);
     }
 
@@ -127,31 +145,14 @@
      * @param weight The weight that should be considered for this view when compared to other
      *               views. This has an impact on the placement of the view but not the rendering of
      *               the view.
-     * @param margin The margin to apply between complications.
-     */
-    public ComplicationLayoutParams(int width, int height, @Position int position,
-            @Direction int direction, int weight, int margin) {
-        this(width, height, position, direction, weight, margin, CONSTRAINT_UNSPECIFIED, false);
-    }
-
-    /**
-     * Constructs a {@link ComplicationLayoutParams}.
-     * @param width The width {@link android.view.View.MeasureSpec} for the view.
-     * @param height The height {@link android.view.View.MeasureSpec} for the view.
-     * @param position The place within the parent container where the view should be positioned.
-     * @param direction The direction the view should be laid out from either the parent container
-     *                  or preceding view.
-     * @param weight The weight that should be considered for this view when compared to other
-     *               views. This has an impact on the placement of the view but not the rendering of
-     *               the view.
-     * @param margin The margin to apply between complications.
+     * @param directionalSpacing The spacing to apply between complications.
      * @param constraint The max width or height the complication is allowed to spread, depending on
      *                   its direction. For horizontal directions, this would be applied on width,
      *                   and for vertical directions, height.
      */
     public ComplicationLayoutParams(int width, int height, @Position int position,
-            @Direction int direction, int weight, int margin, int constraint) {
-        this(width, height, position, direction, weight, margin, constraint, false);
+            @Direction int direction, int weight, int directionalSpacing, int constraint) {
+        this(width, height, position, direction, weight, directionalSpacing, constraint, false);
     }
 
     /**
@@ -172,8 +173,8 @@
      */
     public ComplicationLayoutParams(int width, int height, @Position int position,
             @Direction int direction, int weight, boolean snapToGuide) {
-        this(width, height, position, direction, weight, MARGIN_UNSPECIFIED, CONSTRAINT_UNSPECIFIED,
-                snapToGuide);
+        this(width, height, position, direction, weight, DIRECTIONAL_SPACING_UNSPECIFIED,
+                CONSTRAINT_UNSPECIFIED, snapToGuide);
     }
 
     /**
@@ -186,7 +187,7 @@
      * @param weight The weight that should be considered for this view when compared to other
      *               views. This has an impact on the placement of the view but not the rendering of
      *               the view.
-     * @param margin The margin to apply between complications.
+     * @param directionalSpacing The spacing to apply between complications.
      * @param constraint The max width or height the complication is allowed to spread, depending on
      *                   its direction. For horizontal directions, this would be applied on width,
      *                   and for vertical directions, height.
@@ -197,7 +198,8 @@
      *                    from the end of the parent to the guide.
      */
     public ComplicationLayoutParams(int width, int height, @Position int position,
-            @Direction int direction, int weight, int margin, int constraint, boolean snapToGuide) {
+            @Direction int direction, int weight, int directionalSpacing, int constraint,
+            boolean snapToGuide) {
         super(width, height);
 
         if (!validatePosition(position)) {
@@ -213,7 +215,7 @@
 
         mWeight = weight;
 
-        mMargin = margin;
+        mDirectionalSpacing = directionalSpacing;
 
         mConstraint = constraint;
 
@@ -228,7 +230,7 @@
         mPosition = source.mPosition;
         mDirection = source.mDirection;
         mWeight = source.mWeight;
-        mMargin = source.mMargin;
+        mDirectionalSpacing = source.mDirectionalSpacing;
         mConstraint = source.mConstraint;
         mSnapToGuide = source.mSnapToGuide;
     }
@@ -300,11 +302,12 @@
     }
 
     /**
-     * Returns the margin to apply between complications, or the given default if no margin is
+     * Returns the spacing to apply between complications, or the given default if no spacing is
      * specified.
      */
-    public int getMargin(int defaultMargin) {
-        return mMargin == MARGIN_UNSPECIFIED ? defaultMargin : mMargin;
+    public int getDirectionalSpacing(int defaultSpacing) {
+        return mDirectionalSpacing == DIRECTIONAL_SPACING_UNSPECIFIED
+                ? defaultSpacing : mDirectionalSpacing;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/complication/dagger/ComplicationHostViewModule.java b/packages/SystemUI/src/com/android/systemui/complication/dagger/ComplicationHostViewModule.java
index 1158565..a7d017dd 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/dagger/ComplicationHostViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/complication/dagger/ComplicationHostViewModule.java
@@ -36,11 +36,20 @@
 @Module
 public abstract class ComplicationHostViewModule {
     public static final String SCOPED_COMPLICATIONS_LAYOUT = "scoped_complications_layout";
-    public static final String COMPLICATION_MARGIN_DEFAULT = "complication_margin_default";
+    public static final String COMPLICATION_DIRECTIONAL_SPACING_DEFAULT =
+            "complication_directional_spacing_default";
     public static final String COMPLICATIONS_FADE_OUT_DURATION = "complications_fade_out_duration";
     public static final String COMPLICATIONS_FADE_IN_DURATION = "complications_fade_in_duration";
     public static final String COMPLICATIONS_RESTORE_TIMEOUT = "complication_restore_timeout";
     public static final String COMPLICATIONS_FADE_OUT_DELAY = "complication_fade_out_delay";
+    public static final String COMPLICATION_MARGIN_POSITION_START =
+            "complication_margin_position_start";
+    public static final String COMPLICATION_MARGIN_POSITION_TOP =
+            "complication_margin_position_top";
+    public static final String COMPLICATION_MARGIN_POSITION_END =
+            "complication_margin_position_end";
+    public static final String COMPLICATION_MARGIN_POSITION_BOTTOM =
+            "complication_margin_position_bottom";
 
     /**
      * Generates a {@link ConstraintLayout}, which can host
@@ -58,11 +67,35 @@
     }
 
     @Provides
-    @Named(COMPLICATION_MARGIN_DEFAULT)
+    @Named(COMPLICATION_DIRECTIONAL_SPACING_DEFAULT)
     static int providesComplicationPadding(@Main Resources resources) {
         return resources.getDimensionPixelSize(R.dimen.dream_overlay_complication_margin);
     }
 
+    @Provides
+    @Named(COMPLICATION_MARGIN_POSITION_START)
+    static int providesComplicationMarginPositionStart(@Main Resources resources) {
+        return resources.getDimensionPixelSize(R.dimen.dream_overlay_container_padding_start);
+    }
+
+    @Provides
+    @Named(COMPLICATION_MARGIN_POSITION_TOP)
+    static int providesComplicationMarginPositionTop(@Main Resources resources) {
+        return resources.getDimensionPixelSize(R.dimen.dream_overlay_container_padding_top);
+    }
+
+    @Provides
+    @Named(COMPLICATION_MARGIN_POSITION_END)
+    static int providesComplicationMarginPositionEnd(@Main Resources resources) {
+        return resources.getDimensionPixelSize(R.dimen.dream_overlay_container_padding_end);
+    }
+
+    @Provides
+    @Named(COMPLICATION_MARGIN_POSITION_BOTTOM)
+    static int providesComplicationMarginPositionBottom(@Main Resources resources) {
+        return resources.getDimensionPixelSize(R.dimen.dream_overlay_container_padding_bottom);
+    }
+
     /**
      * Provides the fade out duration for complications.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index d5a4146..f68bd49 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -23,8 +23,6 @@
 import android.hardware.SensorPrivacyManager;
 import android.os.Handler;
 
-import androidx.annotation.Nullable;
-
 import com.android.internal.logging.UiEventLogger;
 import com.android.keyguard.KeyguardViewController;
 import com.android.systemui.battery.BatterySaverModule;
@@ -74,12 +72,12 @@
 import com.android.systemui.statusbar.policy.SensorPrivacyControllerImpl;
 import com.android.systemui.volume.dagger.VolumeModule;
 
-import javax.inject.Named;
-
 import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
 
+import javax.inject.Named;
+
 /**
  * A dagger module for injecting default implementations of components of System UI.
  *
@@ -115,9 +113,8 @@
     @SysUISingleton
     @Provides
     @Named(LEAK_REPORT_EMAIL_NAME)
-    @Nullable
     static String provideLeakReportEmail() {
-        return null;
+        return "";
     }
 
     @Binds
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 0d3503e..07efbfe 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -217,7 +217,7 @@
                         true /* settingDef */,
                         true /* configured */,
                         DozeLog.REASON_SENSOR_TAP,
-                        false /* reports touch coordinates */,
+                        true /* reports touch coordinates */,
                         true /* touchscreen */,
                         false /* ignoresSetting */,
                         dozeParameters.singleTapUsesProx(mDevicePosture) /* requiresProx */,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index b709608..85272a6 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -323,9 +323,7 @@
                     return;
                 }
                 if (isDoubleTap || isTap) {
-                    if (screenX != -1 && screenY != -1) {
-                        mDozeHost.onSlpiTap(screenX, screenY);
-                    }
+                    mDozeHost.onSlpiTap(screenX, screenY);
                     gentleWakeUp(pulseReason);
                 } else if (isPickup) {
                     if (shouldDropPickupEvent())  {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index a4d4a9a..6967e6c 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -710,6 +710,5 @@
 
     // TODO(b/278761837): Tracking Bug
     @JvmField
-    val USE_NEW_ACTIVITY_STARTER = unreleasedFlag(2801, name = "use_new_activity_starter",
-            teamfood = true)
+    val USE_NEW_ACTIVITY_STARTER = releasedFlag(2801, name = "use_new_activity_starter")
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 552e5ea..0a2d05e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyguard;
 
+import static android.app.StatusBarManager.SESSION_KEYGUARD;
 import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT;
 import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
 import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
@@ -101,6 +102,7 @@
 import com.android.app.animation.Interpolators;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.jank.InteractionJankMonitor.Configuration;
+import com.android.internal.logging.UiEventLogger;
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.internal.policy.IKeyguardExitCallback;
 import com.android.internal.policy.IKeyguardStateCallback;
@@ -131,6 +133,7 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.dagger.KeyguardModule;
+import com.android.systemui.log.SessionTracker;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.settings.UserTracker;
@@ -723,6 +726,13 @@
                 }
             }
         }
+
+        @Override
+        public void onStrongAuthStateChanged(int userId) {
+            if (mLockPatternUtils.isUserInLockdown(KeyguardUpdateMonitor.getCurrentUser())) {
+                doKeyguardLocked(null);
+            }
+        }
     };
 
     ViewMediatorCallback mViewMediatorCallback = new ViewMediatorCallback() {
@@ -1181,12 +1191,16 @@
     private Lazy<ScrimController> mScrimControllerLazy;
 
     private FeatureFlags mFeatureFlags;
+    private final UiEventLogger mUiEventLogger;
+    private final SessionTracker mSessionTracker;
 
     /**
      * Injected constructor. See {@link KeyguardModule}.
      */
     public KeyguardViewMediator(
             Context context,
+            UiEventLogger uiEventLogger,
+            SessionTracker sessionTracker,
             UserTracker userTracker,
             FalsingCollector falsingCollector,
             LockPatternUtils lockPatternUtils,
@@ -1270,6 +1284,8 @@
         mDreamCloseAnimationDuration = (int) LOCKSCREEN_ANIMATION_DURATION_MS;
 
         mFeatureFlags = featureFlags;
+        mUiEventLogger = uiEventLogger;
+        mSessionTracker = sessionTracker;
     }
 
     public void userActivity() {
@@ -1660,6 +1676,13 @@
             if (DEBUG) Log.d(TAG, "onStartedWakingUp, seq = " + mDelayedShowingSequence);
             notifyStartedWakingUp();
         }
+        mUiEventLogger.logWithInstanceIdAndPosition(
+                BiometricUnlockController.BiometricUiEvent.STARTED_WAKING_UP,
+                0,
+                null,
+                mSessionTracker.getSessionId(SESSION_KEYGUARD),
+                pmWakeReason
+        );
         mUpdateMonitor.dispatchStartedWakingUp(pmWakeReason);
         maybeSendUserPresentBroadcast();
         Trace.endSection();
@@ -1933,8 +1956,9 @@
      * Enable the keyguard if the settings are appropriate.
      */
     private void doKeyguardLocked(Bundle options) {
-        // if another app is disabling us, don't show
-        if (!mExternallyEnabled) {
+        // if another app is disabling us, don't show unless we're in lockdown mode
+        if (!mExternallyEnabled
+                && !mLockPatternUtils.isUserInLockdown(KeyguardUpdateMonitor.getCurrentUser())) {
             if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled");
 
             mNeedToReshowWhenReenabled = true;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 5e71458..deb8f5d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -21,6 +21,7 @@
 import android.os.PowerManager;
 
 import com.android.internal.jank.InteractionJankMonitor;
+import com.android.internal.logging.UiEventLogger;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardDisplayManager;
 import com.android.keyguard.KeyguardUpdateMonitor;
@@ -50,6 +51,7 @@
 import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceModule;
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger;
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLoggerImpl;
+import com.android.systemui.log.SessionTracker;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeController;
@@ -63,12 +65,12 @@
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.util.DeviceConfigProxy;
 
-import java.util.concurrent.Executor;
-
 import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
 
+import java.util.concurrent.Executor;
+
 /**
  * Dagger Module providing keyguard.
  */
@@ -93,6 +95,8 @@
     @SysUISingleton
     public static KeyguardViewMediator newKeyguardViewMediator(
             Context context,
+            UiEventLogger uiEventLogger,
+            SessionTracker sessionTracker,
             UserTracker userTracker,
             FalsingCollector falsingCollector,
             LockPatternUtils lockPatternUtils,
@@ -124,6 +128,8 @@
             FeatureFlags featureFlags) {
         return new KeyguardViewMediator(
                 context,
+                uiEventLogger,
+                sessionTracker,
                 userTracker,
                 falsingCollector,
                 lockPatternUtils,
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 5b71a2e..9621f03 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
@@ -45,7 +45,6 @@
 import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.TransitionState
-import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import com.android.systemui.log.FaceAuthenticationLogger
 import com.android.systemui.log.SessionTracker
 import com.android.systemui.log.table.TableLogBuffer
@@ -239,9 +238,7 @@
         // Clear auth status when keyguard is going away or when the user is switching or device
         // starts going to sleep.
         merge(
-                keyguardRepository.wakefulness.map {
-                    WakefulnessModel.isSleepingOrStartingToSleep(it)
-                },
+                keyguardRepository.wakefulness.map { it.isStartingToSleepOrAsleep() },
                 keyguardRepository.isKeyguardGoingAway,
                 userRepository.userSwitchingInProgress
             )
@@ -315,9 +312,7 @@
                     tableLogBuffer
                 ),
                 logAndObserve(
-                    keyguardRepository.wakefulness
-                        .map { WakefulnessModel.isSleepingOrStartingToSleep(it) }
-                        .isFalse(),
+                    keyguardRepository.wakefulness.map { it.isStartingToSleepOrAsleep() }.isFalse(),
                     "deviceNotSleepingOrNotStartingToSleep",
                     tableLogBuffer
                 ),
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
index 0b506cf..7c14280 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
@@ -18,7 +18,6 @@
 
 import android.os.Build
 import android.util.Log
-import com.android.keyguard.ViewMediatorCallback
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
@@ -104,7 +103,6 @@
 class KeyguardBouncerRepositoryImpl
 @Inject
 constructor(
-    private val viewMediatorCallback: ViewMediatorCallback,
     private val clock: SystemClock,
     @Application private val applicationScope: CoroutineScope,
     @BouncerLog private val buffer: TableLogBuffer,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 3567d81..742e535 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -26,7 +26,6 @@
 import com.android.systemui.common.shared.model.Position
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.doze.DozeHost
 import com.android.systemui.doze.DozeMachine
 import com.android.systemui.doze.DozeTransitionCallback
 import com.android.systemui.doze.DozeTransitionListener
@@ -105,7 +104,7 @@
      * returns `false`. In order to account for that, observers should also use the
      * [linearDozeAmount] flow to check if it's greater than `0`
      */
-    val isDozing: Flow<Boolean>
+    val isDozing: StateFlow<Boolean>
 
     /**
      * Observable for whether the device is dreaming.
@@ -133,6 +132,8 @@
     /** Doze state information, as it transitions */
     val dozeTransitionModel: Flow<DozeTransitionModel>
 
+    val lastDozeTapToWakePosition: StateFlow<Point?>
+
     /** Observable for the [StatusBarState] */
     val statusBarState: Flow<StatusBarState>
 
@@ -181,6 +182,10 @@
 
     /** Sets whether quick settings or quick-quick settings is visible. */
     fun setQuickSettingsVisible(isVisible: Boolean)
+
+    fun setLastDozeTapToWakePosition(position: Point)
+
+    fun setIsDozing(isDozing: Boolean)
 }
 
 /** Encapsulates application state for the keyguard. */
@@ -189,7 +194,6 @@
 @Inject
 constructor(
     statusBarStateController: StatusBarStateController,
-    dozeHost: DozeHost,
     wakefulnessLifecycle: WakefulnessLifecycle,
     biometricUnlockController: BiometricUnlockController,
     private val keyguardStateController: KeyguardStateController,
@@ -333,24 +337,19 @@
         awaitClose { keyguardStateController.removeCallback(callback) }
     }
 
-    override val isDozing: Flow<Boolean> =
-        conflatedCallbackFlow {
-                val callback =
-                    object : DozeHost.Callback {
-                        override fun onDozingChanged(isDozing: Boolean) {
-                            trySendWithFailureLogging(isDozing, TAG, "updated isDozing")
-                        }
-                    }
-                dozeHost.addCallback(callback)
-                trySendWithFailureLogging(
-                    statusBarStateController.isDozing,
-                    TAG,
-                    "initial isDozing",
-                )
+    private val _isDozing = MutableStateFlow(statusBarStateController.isDozing)
+    override val isDozing: StateFlow<Boolean> = _isDozing.asStateFlow()
 
-                awaitClose { dozeHost.removeCallback(callback) }
-            }
-            .distinctUntilChanged()
+    override fun setIsDozing(isDozing: Boolean) {
+        _isDozing.value = isDozing
+    }
+
+    private val _lastDozeTapToWakePosition = MutableStateFlow<Point?>(null)
+    override val lastDozeTapToWakePosition = _lastDozeTapToWakePosition.asStateFlow()
+
+    override fun setLastDozeTapToWakePosition(position: Point) {
+        _lastDozeTapToWakePosition.value = position
+    }
 
     override val isDreamingWithOverlay: Flow<Boolean> =
         conflatedCallbackFlow {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
index a17481a..482e9a3d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
@@ -24,7 +24,6 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
-import com.android.systemui.keyguard.shared.model.WakeSleepReason
 import com.android.systemui.statusbar.CircleReveal
 import com.android.systemui.statusbar.LiftReveal
 import com.android.systemui.statusbar.LightRevealEffect
@@ -43,7 +42,7 @@
 
 /**
  * Encapsulates state relevant to the light reveal scrim, the view used to reveal/hide screen
- * contents during transitions between AOD and lockscreen/unlocked.
+ * contents during transitions between DOZE or AOD and lockscreen/unlocked.
  */
 interface LightRevealScrimRepository {
 
@@ -64,13 +63,20 @@
 ) : LightRevealScrimRepository {
 
     /** The reveal effect used if the device was locked/unlocked via the power button. */
-    private val powerButtonReveal =
-        PowerButtonReveal(
-            context.resources
-                .getDimensionPixelSize(R.dimen.physical_power_button_center_screen_location_y)
-                .toFloat()
+    private val powerButtonRevealEffect: Flow<LightRevealEffect?> =
+        flowOf(
+            PowerButtonReveal(
+                context.resources
+                    .getDimensionPixelSize(R.dimen.physical_power_button_center_screen_location_y)
+                    .toFloat()
+            )
         )
 
+    private val tapRevealEffect: Flow<LightRevealEffect?> =
+        keyguardRepository.lastDozeTapToWakePosition.map {
+            it?.let { constructCircleRevealFromPoint(it) }
+        }
+
     /**
      * Reveal effect to use for a fingerprint unlock. This is reconstructed if the fingerprint
      * sensor location on the screen (in pixels) changes due to configuration changes.
@@ -102,18 +108,11 @@
 
     /** The reveal effect we'll use for the next non-biometric unlock (tap, power button, etc). */
     private val nonBiometricRevealEffect: Flow<LightRevealEffect?> =
-        keyguardRepository.wakefulness.map { wakefulnessModel ->
-            val wakingUpFromPowerButton =
-                wakefulnessModel.isWakingUpOrAwake &&
-                    wakefulnessModel.lastWakeReason == WakeSleepReason.POWER_BUTTON
-            val sleepingFromPowerButton =
-                !wakefulnessModel.isWakingUpOrAwake &&
-                    wakefulnessModel.lastSleepReason == WakeSleepReason.POWER_BUTTON
-
-            if (wakingUpFromPowerButton || sleepingFromPowerButton) {
-                powerButtonReveal
-            } else {
-                LiftReveal
+        keyguardRepository.wakefulness.flatMapLatest { wakefulnessModel ->
+            when {
+                wakefulnessModel.isTransitioningFromPowerButton() -> powerButtonRevealEffect
+                wakefulnessModel.isAwakeFromTap() -> tapRevealEffect
+                else -> flowOf(LiftReveal)
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt
new file mode 100644
index 0000000..2efcd0c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DozeInteractor.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.graphics.Point
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import javax.inject.Inject
+
+@SysUISingleton
+class DozeInteractor
+@Inject
+constructor(
+    private val keyguardRepository: KeyguardRepository,
+) {
+
+    fun setIsDozing(isDozing: Boolean) {
+        keyguardRepository.setIsDozing(isDozing)
+    }
+
+    fun setLastTapToWakePosition(position: Point) {
+        keyguardRepository.setLastDozeTapToWakePosition(position)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index cde67f9..38eacce 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -24,6 +24,8 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.util.kotlin.Utils.Companion.toQuad
+import com.android.systemui.util.kotlin.Utils.Companion.toQuint
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index aca4019..323fc31 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -24,13 +24,11 @@
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionInfo
-import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isWakingOrStartingToWake
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlin.time.Duration
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.launch
 
 @SysUISingleton
@@ -54,7 +52,7 @@
                 .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { (wakefulnessModel, lastStartedTransition) ->
                     if (
-                        isWakingOrStartingToWake(wakefulnessModel) &&
+                        wakefulnessModel.isStartingToWake() &&
                             lastStartedTransition.to == KeyguardState.DOZING
                     ) {
                         keyguardTransitionRepository.startTransition(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index fc7bfb4..36c8eb1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -26,13 +26,13 @@
 import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlin.time.Duration
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.launch
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index 39c630b..cfcb654 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlin.time.Duration
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 0505d37..b5e289f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -27,6 +27,8 @@
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.WakefulnessState
 import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.util.kotlin.Utils.Companion.toQuad
+import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
 import java.util.UUID
 import javax.inject.Inject
@@ -144,7 +146,7 @@
                         keyguardTransitionInteractor.startedKeyguardTransitionStep,
                         keyguardInteractor.statusBarState,
                         keyguardInteractor.isKeyguardUnlocked,
-                        ::toTriple
+                        ::Triple
                     ),
                     ::toQuad
                 )
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index 47846d1..b0dbc59 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -24,12 +24,12 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlin.time.Duration
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index bc55bd4..da09e1f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.util.kotlin.Utils.Companion.toQuad
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlin.time.Duration
@@ -60,7 +61,7 @@
                         keyguardInteractor.wakefulnessModel,
                         keyguardTransitionInteractor.startedKeyguardTransitionStep,
                         keyguardInteractor.isKeyguardOccluded,
-                        ::toTriple
+                        ::Triple
                     ),
                     ::toQuad
                 )
@@ -100,7 +101,7 @@
                         keyguardInteractor.wakefulnessModel,
                         keyguardTransitionInteractor.startedKeyguardTransitionStep,
                         keyguardInteractor.isAodAvailable,
-                        ::toTriple
+                        ::Triple
                     ),
                     ::toQuad
                 )
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 1ac0c52..3cf9a9e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -33,7 +33,6 @@
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
-import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isWakingOrStartingToWake
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
@@ -108,18 +107,12 @@
      */
     val isAbleToDream: Flow<Boolean> =
         merge(isDreaming, isDreamingWithOverlay)
-            .combine(
-                dozeTransitionModel,
-                { isDreaming, dozeTransitionModel ->
-                    isDreaming && isDozeOff(dozeTransitionModel.to)
-                }
-            )
-            .sample(
-                wakefulnessModel,
-                { isAbleToDream, wakefulnessModel ->
-                    isAbleToDream && isWakingOrStartingToWake(wakefulnessModel)
-                }
-            )
+            .combine(dozeTransitionModel) { isDreaming, dozeTransitionModel ->
+                isDreaming && isDozeOff(dozeTransitionModel.to)
+            }
+            .sample(wakefulnessModel) { isAbleToDream, wakefulnessModel ->
+                isAbleToDream && wakefulnessModel.isStartingToWake()
+            }
             .flatMapLatest { isAbleToDream ->
                 flow {
                     delay(50)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index e3e3527..b7dd1a5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -27,29 +27,5 @@
  * 'when' clause of [KeyguardTransitionCoreStartable]
  */
 sealed class TransitionInteractor(val name: String) {
-
     abstract fun start()
-
-    fun <A, B, C> toTriple(a: A, b: B, c: C) = Triple(a, b, c)
-
-    fun <A, B, C> toTriple(a: A, bc: Pair<B, C>) = Triple(a, bc.first, bc.second)
-
-    fun <A, B, C> toTriple(ab: Pair<A, B>, c: C) = Triple(ab.first, ab.second, c)
-
-    fun <A, B, C, D> toQuad(a: A, b: B, c: C, d: D) = Quad(a, b, c, d)
-
-    fun <A, B, C, D> toQuad(a: A, bcd: Triple<B, C, D>) = Quad(a, bcd.first, bcd.second, bcd.third)
-
-    fun <A, B, C, D, E> toQuint(a: A, bcde: Quad<B, C, D, E>) =
-        Quint(a, bcde.first, bcde.second, bcde.third, bcde.fourth)
 }
-
-data class Quad<A, B, C, D>(val first: A, val second: B, val third: C, val fourth: D)
-
-data class Quint<A, B, C, D, E>(
-    val first: A,
-    val second: B,
-    val third: C,
-    val fourth: D,
-    val fifth: E
-)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt
index b32597d..51ce7ff 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakeSleepReason.kt
@@ -23,6 +23,9 @@
     /** The physical power button was pressed to wake up or sleep the device. */
     POWER_BUTTON,
 
+    /** The user has taped or double tapped to wake the screen */
+    TAP,
+
     /** Something else happened to wake up or sleep the device. */
     OTHER;
 
@@ -30,6 +33,7 @@
         fun fromPowerManagerWakeReason(reason: Int): WakeSleepReason {
             return when (reason) {
                 PowerManager.WAKE_REASON_POWER_BUTTON -> POWER_BUTTON
+                PowerManager.WAKE_REASON_TAP -> TAP
                 else -> OTHER
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
index 03dee00..7ca90ba 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
@@ -20,26 +20,31 @@
 /** Model device wakefulness states. */
 data class WakefulnessModel(
     val state: WakefulnessState,
-    val isWakingUpOrAwake: Boolean,
     val lastWakeReason: WakeSleepReason,
     val lastSleepReason: WakeSleepReason,
 ) {
+    fun isStartingToWake() = state == WakefulnessState.STARTING_TO_WAKE
+
+    fun isStartingToSleep() = state == WakefulnessState.STARTING_TO_SLEEP
+
+    fun isStartingToSleepOrAsleep() = isStartingToSleep() || state == WakefulnessState.ASLEEP
+
+    fun isStartingToSleepFromPowerButton() =
+        isStartingToSleep() && lastWakeReason == WakeSleepReason.POWER_BUTTON
+
+    fun isWakingFromPowerButton() =
+        isStartingToWake() && lastWakeReason == WakeSleepReason.POWER_BUTTON
+
+    fun isTransitioningFromPowerButton() =
+        isStartingToSleepFromPowerButton() || isWakingFromPowerButton()
+
+    fun isAwakeFromTap() =
+        state == WakefulnessState.STARTING_TO_WAKE && lastWakeReason == WakeSleepReason.TAP
+
     companion object {
-        fun isSleepingOrStartingToSleep(model: WakefulnessModel): Boolean {
-            return model.state == WakefulnessState.ASLEEP ||
-                model.state == WakefulnessState.STARTING_TO_SLEEP
-        }
-
-        fun isWakingOrStartingToWake(model: WakefulnessModel): Boolean {
-            return model.state == WakefulnessState.AWAKE ||
-                model.state == WakefulnessState.STARTING_TO_WAKE
-        }
-
         fun fromWakefulnessLifecycle(wakefulnessLifecycle: WakefulnessLifecycle): WakefulnessModel {
             return WakefulnessModel(
                 WakefulnessState.fromWakefulnessLifecycleInt(wakefulnessLifecycle.wakefulness),
-                wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_WAKING ||
-                    wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_AWAKE,
                 WakeSleepReason.fromPowerManagerWakeReason(wakefulnessLifecycle.lastWakeReason),
                 WakeSleepReason.fromPowerManagerSleepReason(wakefulnessLifecycle.lastSleepReason),
             )
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/CarrierTextManagerLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/CarrierTextManagerLog.kt
new file mode 100644
index 0000000..62b80b2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/CarrierTextManagerLog.kt
@@ -0,0 +1,9 @@
+package com.android.systemui.log.dagger
+
+import javax.inject.Qualifier
+
+/** A [LogBuffer] for detailed carrier text logs. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class CarrierTextManagerLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 9be18ac..66c3c02 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -373,6 +373,16 @@
     }
 
     /**
+     * Provides a {@link LogBuffer} for use by {@link com.android.keyguard.KeyguardUpdateMonitor}.
+     */
+    @Provides
+    @SysUISingleton
+    @CarrierTextManagerLog
+    public static LogBuffer provideCarrierTextManagerLog(LogBufferFactory factory) {
+        return factory.create("CarrierTextManagerLog", 100);
+    }
+
+    /**
      * Provides a {@link LogBuffer} for use by {@link com.android.systemui.ScreenDecorations}.
      */
     @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index 385e720..2469a98 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -42,7 +42,9 @@
 import androidx.annotation.WorkerThread;
 
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import dagger.assisted.Assisted;
 import dagger.assisted.AssistedFactory;
@@ -88,6 +90,7 @@
     private final Handler mHandler;
     private final Intent mIntent;
     private final UserHandle mUser;
+    private final DelayableExecutor mExecutor;
     private final IBinder mToken = new Binder();
     private final PackageManagerAdapter mPackageManagerAdapter;
     private final BroadcastDispatcher mBroadcastDispatcher;
@@ -100,25 +103,27 @@
 
     private int mBindTryCount;
     private int mBindRetryDelay = DEFAULT_BIND_RETRY_DELAY;
-    private boolean mBound;
+    private AtomicBoolean mBound = new AtomicBoolean(false);
     private AtomicBoolean mPackageReceiverRegistered = new AtomicBoolean(false);
     private AtomicBoolean mUserReceiverRegistered = new AtomicBoolean(false);
-    private boolean mUnbindImmediate;
+    private AtomicBoolean mUnbindImmediate = new AtomicBoolean(false);
     @Nullable
     private TileChangeListener mChangeListener;
     // Return value from bindServiceAsUser, determines whether safe to call unbind.
-    private boolean mIsBound;
+    private AtomicBoolean mIsBound = new AtomicBoolean(false);
 
     @AssistedInject
     TileLifecycleManager(@Main Handler handler, Context context, IQSService service,
             PackageManagerAdapter packageManagerAdapter, BroadcastDispatcher broadcastDispatcher,
-            @Assisted Intent intent, @Assisted UserHandle user) {
+            @Assisted Intent intent, @Assisted UserHandle user,
+            @Background DelayableExecutor executor) {
         mContext = context;
         mHandler = handler;
         mIntent = intent;
         mIntent.putExtra(TileService.EXTRA_SERVICE, service.asBinder());
         mIntent.putExtra(TileService.EXTRA_TOKEN, mToken);
         mUser = user;
+        mExecutor = executor;
         mPackageManagerAdapter = packageManagerAdapter;
         mBroadcastDispatcher = broadcastDispatcher;
         if (DEBUG) Log.d(TAG, "Creating " + mIntent + " " + mUser);
@@ -184,22 +189,21 @@
      * Binds just long enough to send any queued messages, then unbinds.
      */
     public void flushMessagesAndUnbind() {
-        mUnbindImmediate = true;
-        setBindService(true);
+        mExecutor.execute(() -> {
+            mUnbindImmediate.set(true);
+            setBindService(true);
+        });
     }
 
-    /**
-     * Binds or unbinds to IQSService
-     */
     @WorkerThread
-    public void setBindService(boolean bind) {
-        if (mBound && mUnbindImmediate) {
+    private void setBindService(boolean bind) {
+        if (mBound.get() && mUnbindImmediate.get()) {
             // If we are already bound and expecting to unbind, this means we should stay bound
             // because something else wants to hold the connection open.
-            mUnbindImmediate = false;
+            mUnbindImmediate.set(false);
             return;
         }
-        mBound = bind;
+        mBound.set(bind);
         if (bind) {
             if (mBindTryCount == MAX_BIND_RETRIES) {
                 // Too many failures, give up on this tile until an update.
@@ -212,31 +216,38 @@
             if (DEBUG) Log.d(TAG, "Binding service " + mIntent + " " + mUser);
             mBindTryCount++;
             try {
-                mIsBound = bindServices();
-                if (!mIsBound) {
+                mIsBound.set(bindServices());
+                if (!mIsBound.get()) {
                     mContext.unbindService(this);
                 }
             } catch (SecurityException e) {
                 Log.e(TAG, "Failed to bind to service", e);
-                mIsBound = false;
+                mIsBound.set(false);
             }
         } else {
             if (DEBUG) Log.d(TAG, "Unbinding service " + mIntent + " " + mUser);
             // Give it another chance next time it needs to be bound, out of kindness.
             mBindTryCount = 0;
             freeWrapper();
-            if (mIsBound) {
+            if (mIsBound.get()) {
                 try {
                     mContext.unbindService(this);
                 } catch (Exception e) {
                     Log.e(TAG, "Failed to unbind service "
                             + mIntent.getComponent().flattenToShortString(), e);
                 }
-                mIsBound = false;
+                mIsBound.set(false);
             }
         }
     }
 
+    /**
+     * Binds or unbinds to IQSService
+     */
+    public void executeSetBindService(boolean bind) {
+        mExecutor.execute(() -> setBindService(bind));
+    }
+
     private boolean bindServices() {
         String packageName = mIntent.getComponent().getPackageName();
         if (CompatChanges.isChangeEnabled(START_ACTIVITY_NEEDS_PENDING_INTENT, packageName,
@@ -317,10 +328,12 @@
             }
             onTileRemoved();
         }
-        if (mUnbindImmediate) {
-            mUnbindImmediate = false;
-            setBindService(false);
-        }
+        mExecutor.execute(() -> {
+            if (mUnbindImmediate.get()) {
+                mUnbindImmediate.set(false);
+                setBindService(false);
+            }
+        });
     }
 
     public void handleDestroy() {
@@ -335,19 +348,11 @@
         if (mWrapper == null) return;
         freeWrapper();
         // Clearly not bound anymore
-        mIsBound = false;
-        if (!mBound) return;
+        mIsBound.set(false);
+        if (!mBound.get()) return;
         if (DEBUG) Log.d(TAG, "handleDeath");
         if (checkComponentState()) {
-            mHandler.postDelayed(new Runnable() {
-                @Override
-                public void run() {
-                    if (mBound) {
-                        // Retry binding.
-                        setBindService(true);
-                    }
-                }
-            }, mBindRetryDelay);
+            mExecutor.executeDelayed(() -> setBindService(true), mBindRetryDelay);
         }
     }
 
@@ -410,11 +415,15 @@
             mChangeListener.onTileChanged(mIntent.getComponent());
         }
         stopPackageListening();
-        if (mBound) {
-            // Trying to bind again will check the state of the package before bothering to bind.
-            if (DEBUG) Log.d(TAG, "Trying to rebind");
-            setBindService(true);
-        }
+        mExecutor.execute(() -> {
+            if (mBound.get()) {
+                // Trying to bind again will check the state of the package before bothering to
+                // bind.
+                if (DEBUG) Log.d(TAG, "Trying to rebind");
+                setBindService(true);
+            }
+
+        });
     }
 
     private boolean isComponentAvailable() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
index 7a10a27..941a9d6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
@@ -35,6 +35,7 @@
 import com.android.systemui.qs.external.TileLifecycleManager.TileChangeListener;
 import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import java.util.List;
 import java.util.Objects;
@@ -75,12 +76,12 @@
 
     TileServiceManager(TileServices tileServices, Handler handler, ComponentName component,
             BroadcastDispatcher broadcastDispatcher, UserTracker userTracker,
-            CustomTileAddedRepository customTileAddedRepository) {
+            CustomTileAddedRepository customTileAddedRepository, DelayableExecutor executor) {
         this(tileServices, handler, userTracker, customTileAddedRepository,
                 new TileLifecycleManager(handler, tileServices.getContext(), tileServices,
-                new PackageManagerAdapter(tileServices.getContext()), broadcastDispatcher,
-                new Intent(TileService.ACTION_QS_TILE).setComponent(component),
-                userTracker.getUserHandle()));
+                        new PackageManagerAdapter(tileServices.getContext()), broadcastDispatcher,
+                        new Intent(TileService.ACTION_QS_TILE).setComponent(component),
+                        userTracker.getUserHandle(), executor));
     }
 
     @VisibleForTesting
@@ -203,7 +204,7 @@
         mBound = true;
         mJustBound = true;
         mHandler.postDelayed(mJustBoundOver, MIN_BIND_TIME);
-        mStateManager.setBindService(true);
+        mStateManager.executeSetBindService(true);
     }
 
     private void unbindService() {
@@ -213,7 +214,7 @@
         }
         mBound = false;
         mJustBound = false;
-        mStateManager.setBindService(false);
+        mStateManager.executeSetBindService(false);
     }
 
     public void calculateBindPriority(long currentTime) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
index 121955c..c8691ac 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -39,6 +39,7 @@
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository;
@@ -47,6 +48,7 @@
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -79,6 +81,7 @@
     private final StatusBarIconController mStatusBarIconController;
     private final PanelInteractor mPanelInteractor;
     private final CustomTileAddedRepository mCustomTileAddedRepository;
+    private final DelayableExecutor mBackgroundExecutor;
 
     private int mMaxBound = DEFAULT_MAX_BOUND;
 
@@ -92,7 +95,8 @@
             CommandQueue commandQueue,
             StatusBarIconController statusBarIconController,
             PanelInteractor panelInteractor,
-            CustomTileAddedRepository customTileAddedRepository) {
+            CustomTileAddedRepository customTileAddedRepository,
+            @Background DelayableExecutor backgroundExecutor) {
         mHost = host;
         mKeyguardStateController = keyguardStateController;
         mContext = mHost.getContext();
@@ -105,6 +109,7 @@
         mCommandQueue.addCallback(mRequestListeningCallback);
         mPanelInteractor = panelInteractor;
         mCustomTileAddedRepository = customTileAddedRepository;
+        mBackgroundExecutor = backgroundExecutor;
     }
 
     public Context getContext() {
@@ -132,7 +137,7 @@
     protected TileServiceManager onCreateTileService(ComponentName component,
             BroadcastDispatcher broadcastDispatcher) {
         return new TileServiceManager(this, mHandlerProvider.get(), component,
-                broadcastDispatcher, mUserTracker, mCustomTileAddedRepository);
+                broadcastDispatcher, mUserTracker, mCustomTileAddedRepository, mBackgroundExecutor);
     }
 
     public void freeService(CustomTile tile, TileServiceManager service) {
@@ -339,9 +344,26 @@
             verifyCaller(customTile);
             return customTile.getQsTile();
         }
+        Log.e(TAG, "Tile for token " + token + "not found. "
+                + "Tiles in map: " + availableTileComponents());
         return null;
     }
 
+    private String availableTileComponents() {
+        StringBuilder sb = new StringBuilder("[");
+        synchronized (mServices) {
+            mTokenMap.forEach((iBinder, customTile) ->
+                    sb.append(iBinder.toString())
+                    .append(":")
+                    .append(customTile.getComponent().flattenToShortString())
+                    .append(":")
+                    .append(customTile.getUser())
+                    .append(","));
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+
     @Override
     public void startUnlockAndRun(IBinder token) {
         CustomTile customTile = getTileForToken(token);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
index e026bdb..544e6ad 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.tiles;
 
+import static android.graphics.drawable.Icon.TYPE_URI;
 import static android.provider.Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT;
 
 import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE;
@@ -225,7 +226,12 @@
                 return;
             }
             mSelectedCard = cards.get(selectedIndex);
-            mCardViewDrawable = mSelectedCard.getCardImage().loadDrawable(mContext);
+            android.graphics.drawable.Icon cardImageIcon = mSelectedCard.getCardImage();
+            if (cardImageIcon.getType() == TYPE_URI) {
+                mCardViewDrawable = null;
+            } else {
+                mCardViewDrawable = mSelectedCard.getCardImage().loadDrawable(mContext);
+            }
             refreshState();
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index a43f520..07259c2 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -205,8 +205,11 @@
                 // TODO move this logic to message queue
                 mCentralSurfacesOptionalLazy.get().ifPresent(centralSurfaces -> {
                     if (event.getActionMasked() == ACTION_DOWN) {
-                        centralSurfaces.getShadeViewController()
-                                        .startExpandLatencyTracking();
+                        ShadeViewController shadeViewController =
+                                centralSurfaces.getShadeViewController();
+                        if (shadeViewController != null) {
+                            shadeViewController.startExpandLatencyTracking();
+                        }
                     }
                     mHandler.post(() -> {
                         int action = event.getActionMasked();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 926ede9..af12bc2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -4373,7 +4373,8 @@
 
                 @Override
                 public boolean shouldHeadsUpBeVisible() {
-                    return mHeadsUpAppearanceController.shouldBeVisible();
+                    return mHeadsUpAppearanceController != null &&
+                            mHeadsUpAppearanceController.shouldBeVisible();
                 }
 
                 @Override
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
index 20313c3..a048f54 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
@@ -54,12 +54,20 @@
      * Listener will also be immediately notified with the current values.
      */
     fun addExpansionListener(listener: ShadeExpansionListener) {
-        expansionListeners.add(listener)
+        addShadeExpansionListener(listener)
         listener.onPanelExpansionChanged(
             ShadeExpansionChangeEvent(fraction, expanded, tracking, dragDownPxAmount)
         )
     }
 
+    /**
+     * Adds a listener that will be notified when the panel expansion fraction has changed.
+     * @see #addExpansionListener
+     */
+    fun addShadeExpansionListener(listener: ShadeExpansionListener) {
+        expansionListeners.add(listener)
+    }
+
     /** Removes an expansion listener. */
     fun removeExpansionListener(listener: ShadeExpansionListener) {
         expansionListeners.remove(listener)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java
index 12f2c22..f84b96c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java
@@ -15,13 +15,14 @@
  */
 package com.android.systemui.statusbar.connectivity;
 
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN;
 import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT;
 import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT;
 
 import android.content.Context;
 import android.content.Intent;
-import android.net.NetworkCapabilities;
 import android.net.wifi.WifiManager;
 import android.os.Handler;
 import android.text.Html;
@@ -37,6 +38,7 @@
 import com.android.systemui.dagger.qualifiers.Background;
 
 import java.io.PrintWriter;
+import java.util.BitSet;
 
 /** */
 public class WifiSignalController extends SignalController<WifiState, IconGroup> {
@@ -56,8 +58,12 @@
             WifiManager wifiManager,
             WifiStatusTrackerFactory trackerFactory,
             @Background Handler bgHandler) {
-        super("WifiSignalController", context, NetworkCapabilities.TRANSPORT_WIFI,
-                callbackHandler, networkController);
+        super(
+                "WifiSignalController",
+                context,
+                TRANSPORT_WIFI,
+                callbackHandler,
+                networkController);
         mBgHandler = bgHandler;
         mWifiManager = wifiManager;
         mWifiTracker = trackerFactory.createTracker(this::handleStatusUpdated, bgHandler);
@@ -160,7 +166,10 @@
         // The WiFi signal level returned by WifiManager#calculateSignalLevel start from 0, so
         // WifiManager#getMaxSignalLevel + 1 represents the total level buckets count.
         int totalLevel = mWifiManager.getMaxSignalLevel() + 1;
-        boolean noInternet = mCurrentState.inetCondition == 0;
+        // A carrier merged connection could come from a WIFI *or* CELLULAR transport, so we can't
+        // use [mCurrentState.inetCondition], which only checks the WIFI status. Instead, check if
+        // the default connection is validated at all.
+        boolean noInternet = !mCurrentState.isDefaultConnectionValidated;
         if (mCurrentState.connected) {
             return SignalDrawable.getState(level, totalLevel, noInternet);
         } else if (mCurrentState.enabled) {
@@ -236,6 +245,18 @@
                 && mCurrentState.isCarrierMerged && (mCurrentState.subId == subId);
     }
 
+    @Override
+    void updateConnectivity(BitSet connectedTransports, BitSet validatedTransports) {
+        mCurrentState.inetCondition = validatedTransports.get(mTransportType) ? 1 : 0;
+        // Because a carrier merged connection can come from either a CELLULAR *or* WIFI transport,
+        // we need to also store if either transport is validated to correctly display the carrier
+        // merged case.
+        mCurrentState.isDefaultConnectionValidated =
+                validatedTransports.get(TRANSPORT_CELLULAR)
+                        || validatedTransports.get(TRANSPORT_WIFI);
+        notifyListenersIfNecessary();
+    }
+
     @VisibleForTesting
     void setActivity(int wifiActivity) {
         mCurrentState.activityIn = wifiActivity == DATA_ACTIVITY_INOUT
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiState.kt
index d32e349..63a63de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiState.kt
@@ -24,6 +24,14 @@
     @JvmField var isDefault: Boolean = false,
     @JvmField var statusLabel: String? = null,
     @JvmField var isCarrierMerged: Boolean = false,
+    /**
+     * True if the current default connection is validated for *any* transport, not just wifi.
+     * (Specifically TRANSPORT_CELLULAR *or* TRANSPORT_WIFI.)
+     *
+     * This should *only* be used when calculating information for the carrier merged connection and
+     * *not* for typical wifi connections. See b/225902574.
+     */
+    @JvmField var isDefaultConnectionValidated: Boolean = false,
     @JvmField var subId: Int = 0
 ) : ConnectivityState() {
 
@@ -35,6 +43,7 @@
         isDefault = state.isDefault
         statusLabel = state.statusLabel
         isCarrierMerged = state.isCarrierMerged
+        isDefaultConnectionValidated = state.isDefaultConnectionValidated
         subId = state.subId
     }
 
@@ -45,6 +54,7 @@
                 .append(",isDefault=").append(isDefault)
                 .append(",statusLabel=").append(statusLabel)
                 .append(",isCarrierMerged=").append(isCarrierMerged)
+                .append(",isDefaultConnectionValidated=").append(isDefaultConnectionValidated)
                 .append(",subId=").append(subId)
     }
 
@@ -54,6 +64,7 @@
                 "isDefault",
                 "statusLabel",
                 "isCarrierMerged",
+                "isDefaultConnectionValidated",
                 "subId")
 
         return super.tableColumns() + columns
@@ -65,6 +76,7 @@
         isDefault,
         statusLabel,
         isCarrierMerged,
+        isDefaultConnectionValidated,
         subId).map {
             it.toString()
         }
@@ -84,6 +96,7 @@
         if (isDefault != other.isDefault) return false
         if (statusLabel != other.statusLabel) return false
         if (isCarrierMerged != other.isCarrierMerged) return false
+        if (isDefaultConnectionValidated != other.isDefaultConnectionValidated) return false
         if (subId != other.subId) return false
 
         return true
@@ -96,6 +109,7 @@
         result = 31 * result + isDefault.hashCode()
         result = 31 * result + (statusLabel?.hashCode() ?: 0)
         result = 31 * result + isCarrierMerged.hashCode()
+        result = 31 * result + isDefaultConnectionValidated.hashCode()
         result = 31 * result + subId
         return result
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GenericGestureDetector.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GenericGestureDetector.kt
index 92a8356..1aeb6b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GenericGestureDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GenericGestureDetector.kt
@@ -22,7 +22,6 @@
 import android.view.Choreographer
 import android.view.InputEvent
 import android.view.MotionEvent
-import com.android.systemui.settings.DisplayTracker
 import com.android.systemui.shared.system.InputChannelCompat
 import com.android.systemui.shared.system.InputMonitorCompat
 
@@ -39,7 +38,7 @@
  */
 abstract class GenericGestureDetector(
     private val tag: String,
-    private val displayTracker: DisplayTracker
+    private val displayId: Int,
 ) {
     /**
      * Active callbacks, each associated with a tag. Gestures will only be monitored if
@@ -87,7 +86,7 @@
     internal open fun startGestureListening() {
         stopGestureListening()
 
-        inputMonitor = InputMonitorCompat(tag, displayTracker.defaultDisplayId).also {
+        inputMonitor = InputMonitorCompat(tag, displayId).also {
             inputReceiver = it.getInputReceiver(
                 Looper.getMainLooper(),
                 Choreographer.getInstance(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureHandler.kt
index 6d60f4a9..2fd0a53 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureHandler.kt
@@ -36,7 +36,10 @@
     displayTracker: DisplayTracker,
     private val logger: SwipeUpGestureLogger,
     private val loggerTag: String,
-) : GenericGestureDetector(SwipeUpGestureHandler::class.simpleName!!, displayTracker) {
+) : GenericGestureDetector(
+        SwipeUpGestureHandler::class.simpleName!!,
+        displayTracker.defaultDisplayId
+) {
 
     private var startY: Float = 0f
     private var startTime: Long = 0L
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/TapGestureDetector.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/TapGestureDetector.kt
index a901d597..ed30f2fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/TapGestureDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/TapGestureDetector.kt
@@ -32,7 +32,10 @@
 class TapGestureDetector @Inject constructor(
     private val context: Context,
     displayTracker: DisplayTracker
-) : GenericGestureDetector(TapGestureDetector::class.simpleName!!, displayTracker) {
+) : GenericGestureDetector(
+        TapGestureDetector::class.simpleName!!,
+        displayTracker.defaultDisplayId
+) {
 
     private val gestureListener = object : GestureDetector.SimpleOnGestureListener() {
         override fun onSingleTapUp(e: MotionEvent): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ShadeViewRefactor.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ShadeViewRefactor.java
deleted file mode 100644
index 5ad2ba9..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ShadeViewRefactor.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.notification;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-@Retention(RetentionPolicy.SOURCE)
-public @interface ShadeViewRefactor {
-  /**
-   * Returns the refactor component.
-   * @return the refactor component.
-   */
-  RefactorComponent value();
-
-  public enum RefactorComponent {
-    ADAPTER,
-    LAYOUT_ALGORITHM,
-    STATE_RESOLVER,
-    DECORATOR,
-    INPUT,
-    COORDINATOR,
-    SHADE_VIEW
-  }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
index 9a33a94..2d0395a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
@@ -27,7 +27,6 @@
 import android.view.animation.Interpolator;
 
 import com.android.app.animation.Interpolators;
-import com.android.systemui.statusbar.notification.ShadeViewRefactor;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 
 /**
@@ -90,7 +89,6 @@
     }
 
 
-    @ShadeViewRefactor(ShadeViewRefactor.RefactorComponent.STATE_RESOLVER)
     private void startTopAnimation(boolean animate) {
         int previousEndValue = mEndAnimationRect.top;
         int newEndValue = mBounds.top;
@@ -139,7 +137,6 @@
         mTopAnimator = animator;
     }
 
-    @ShadeViewRefactor(ShadeViewRefactor.RefactorComponent.STATE_RESOLVER)
     private void startBottomAnimation(boolean animate) {
         int previousStartValue = mStartAnimationRect.bottom;
         int previousEndValue = mEndAnimationRect.bottom;
@@ -188,13 +185,11 @@
         mBottomAnimator = animator;
     }
 
-    @ShadeViewRefactor(ShadeViewRefactor.RefactorComponent.SHADE_VIEW)
     private void setBackgroundTop(int top) {
         mCurrentBounds.top = top;
         mOwningView.invalidate();
     }
 
-    @ShadeViewRefactor(ShadeViewRefactor.RefactorComponent.SHADE_VIEW)
     private void setBackgroundBottom(int bottom) {
         mCurrentBounds.bottom = bottom;
         mOwningView.invalidate();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index cf051fb..b81cb2b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -101,8 +101,6 @@
 import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
 import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorController;
 import com.android.systemui.statusbar.notification.NotificationUtils;
-import com.android.systemui.statusbar.notification.ShadeViewRefactor;
-import com.android.systemui.statusbar.notification.ShadeViewRefactor.RefactorComponent;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
@@ -679,7 +677,6 @@
     }
 
     @Override
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     protected void onFinishInflate() {
         super.onFinishInflate();
 
@@ -740,7 +737,6 @@
     }
 
     @VisibleForTesting
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void updateFooter() {
         if (mFooterView == null) {
             return;
@@ -773,12 +769,10 @@
     /**
      * Return whether there are any clearable notifications
      */
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     boolean hasActiveClearableNotifications(@SelectedRows int selection) {
         return mController.hasActiveClearableNotifications(selection);
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public NotificationSwipeActionHelper getSwipeActionHelper() {
         return mSwipeHelper;
     }
@@ -795,7 +789,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.DECORATOR)
     protected void onDraw(Canvas canvas) {
         if (mShouldDrawNotificationBackground
                 && (mSections[0].getCurrentBounds().top
@@ -892,7 +885,6 @@
         return textY;
     }
 
-    @ShadeViewRefactor(RefactorComponent.DECORATOR)
     private void drawBackground(Canvas canvas) {
         int lockScreenLeft = mSidePaddings;
         int lockScreenRight = getWidth() - mSidePaddings;
@@ -1020,7 +1012,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     void updateBackgroundDimming() {
         // No need to update the background color if it's not being drawn.
         if (!mShouldDrawNotificationBackground) {
@@ -1043,7 +1034,6 @@
         initView(getContext(), mSwipeHelper, mNotificationStackSizeCalculator);
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     void initView(Context context, NotificationSwipeHelper swipeHelper,
                   NotificationStackSizeCalculator notificationStackSizeCalculator) {
         mScroller = new OverScroller(getContext());
@@ -1104,12 +1094,10 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private void notifyHeightChangeListener(ExpandableView view) {
         notifyHeightChangeListener(view, false /* needsAnimation */);
     }
 
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private void notifyHeightChangeListener(ExpandableView view, boolean needsAnimation) {
         if (mOnHeightChangedListener != null) {
             mOnHeightChangedListener.onHeightChanged(view, needsAnimation);
@@ -1151,7 +1139,6 @@
     }
 
     @Override
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         Trace.beginSection("NotificationStackScrollLayout#onMeasure");
         if (SPEW) {
@@ -1185,7 +1172,6 @@
     }
 
     @Override
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         // we layout all our children centered on the top
         float centerX = getWidth() / 2.0f;
@@ -1216,7 +1202,6 @@
         mAnimateStackYForContentHeightChange = false;
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void requestAnimationOnViewResize(ExpandableNotificationRow row) {
         if (mAnimationsEnabled && (mIsExpanded || row != null && row.isPinned())) {
             mNeedViewResizeAnimation = true;
@@ -1224,19 +1209,16 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     public void setChildLocationsChangedListener(
             NotificationLogger.OnChildLocationsChangedListener listener) {
         mListener = listener;
     }
 
-    @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM)
     private void setMaxLayoutHeight(int maxLayoutHeight) {
         mMaxLayoutHeight = maxLayoutHeight;
         updateAlgorithmHeightAndPadding();
     }
 
-    @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM)
     private void updateAlgorithmHeightAndPadding() {
         mAmbientState.setLayoutHeight(getLayoutHeight());
         mAmbientState.setLayoutMaxHeight(mMaxLayoutHeight);
@@ -1244,7 +1226,6 @@
         mAmbientState.setTopPadding(mTopPadding);
     }
 
-    @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM)
     private void updateAlgorithmLayoutMinHeight() {
         mAmbientState.setLayoutMinHeight(mQsFullScreen || isHeadsUpTransition()
                 ? getLayoutMinHeight() : 0);
@@ -1254,7 +1235,6 @@
      * Updates the children views according to the stack scroll algorithm. Call this whenever
      * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout.
      */
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void updateChildren() {
         updateScrollStateForAddedChildren();
         mAmbientState.setCurrentScrollVelocity(mScroller.isFinished()
@@ -1268,7 +1248,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     private void onPreDrawDuringAnimation() {
         mShelf.updateAppearance();
         if (!mNeedsAnimation && !mChildrenUpdateRequested) {
@@ -1276,7 +1255,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void updateScrollStateForAddedChildren() {
         if (mChildrenToAddAnimated.isEmpty()) {
             return;
@@ -1297,7 +1275,6 @@
         clampScrollPosition();
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     private void updateForcedScroll() {
         if (mForcedScroll != null && (!mForcedScroll.hasFocus()
                 || !mForcedScroll.isAttachedToWindow())) {
@@ -1317,7 +1294,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     void requestChildrenUpdate() {
         if (!mChildrenUpdateRequested) {
             getViewTreeObserver().addOnPreDrawListener(mChildrenUpdater);
@@ -1326,12 +1302,10 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private boolean isCurrentlyAnimating() {
         return mStateAnimator.isRunning();
     }
 
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private void clampScrollPosition() {
         int scrollRange = getScrollRange();
         if (scrollRange < mOwnScrollY && !mAmbientState.isClearAllInProgress()) {
@@ -1342,12 +1316,10 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public int getTopPadding() {
         return mTopPadding;
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     private void setTopPadding(int topPadding, boolean animate) {
         if (mTopPadding != topPadding) {
             boolean shouldAnimate = animate || mAnimateNextTopPaddingChange;
@@ -1469,7 +1441,6 @@
      *
      * @param height the expanded height of the panel
      */
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     public void setExpandedHeight(float height) {
         final boolean skipHeightUpdate = shouldSkipHeightUpdate();
         updateStackPosition();
@@ -1563,7 +1534,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private void setRequestedClipBounds(Rect clipRect) {
         mRequestedClipBounds = clipRect;
         updateClipping();
@@ -1572,12 +1542,10 @@
     /**
      * Return the height of the content ignoring the footer.
      */
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     public int getIntrinsicContentHeight() {
         return (int) mIntrinsicContentHeight;
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     public void updateClipping() {
         boolean clipped = mRequestedClipBounds != null && !mInHeadsUpPinnedMode
                 && !mHeadsUpAnimatingAway;
@@ -1603,7 +1571,6 @@
      * @return The translation at the beginning when expanding.
      * Measured relative to the resting position.
      */
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private float getExpandTranslationStart() {
         return -mTopPadding + getMinExpansionHeight() - mShelf.getIntrinsicHeight();
     }
@@ -1612,7 +1579,6 @@
      * @return the position from where the appear transition starts when expanding.
      * Measured in absolute height.
      */
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private float getAppearStartPosition() {
         if (isHeadsUpTransition()) {
             final NotificationSection firstVisibleSection = getFirstVisibleSection();
@@ -1629,7 +1595,6 @@
      * intrinsic height, which also includes whether the notification is system expanded and
      * is mainly used when dragging down from a heads up notification.
      */
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private int getTopHeadsUpPinnedHeight() {
         if (mTopHeadsUpEntry == null) {
             return 0;
@@ -1649,7 +1614,6 @@
      * @return the position from where the appear transition ends when expanding.
      * Measured in absolute height.
      */
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private float getAppearEndPosition() {
         int appearPosition = mAmbientState.getStackTopMargin();
         int visibleNotifCount = mController.getVisibleNotificationCount();
@@ -1670,13 +1634,11 @@
         return appearPosition + (onKeyguard() ? mTopPadding : mIntrinsicPadding);
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     private boolean isHeadsUpTransition() {
         return mAmbientState.getTrackedHeadsUpRow() != null;
     }
 
     // TODO(b/246353296): remove it when Flags.SIMPLIFIED_APPEAR_FRACTION is removed
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     public float calculateAppearFractionOld(float height) {
         float appearEndPosition = getAppearEndPosition();
         float appearStartPosition = getAppearStartPosition();
@@ -1718,12 +1680,10 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     public float getStackTranslation() {
         return mStackTranslation;
     }
 
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private void setStackTranslation(float stackTranslation) {
         if (stackTranslation != mStackTranslation) {
             mStackTranslation = stackTranslation;
@@ -1738,17 +1698,14 @@
      *
      * @return either the layout height or the externally defined height, whichever is smaller
      */
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     private int getLayoutHeight() {
         return Math.min(mMaxLayoutHeight, mCurrentStackHeight);
     }
 
-    @ShadeViewRefactor(RefactorComponent.ADAPTER)
     public void setQsHeader(ViewGroup qsHeader) {
         mQsHeader = qsHeader;
     }
 
-    @ShadeViewRefactor(RefactorComponent.ADAPTER)
     public static boolean isPinnedHeadsUp(View v) {
         if (v instanceof ExpandableNotificationRow) {
             ExpandableNotificationRow row = (ExpandableNotificationRow) v;
@@ -1757,7 +1714,6 @@
         return false;
     }
 
-    @ShadeViewRefactor(RefactorComponent.ADAPTER)
     private boolean isHeadsUp(View v) {
         if (v instanceof ExpandableNotificationRow) {
             ExpandableNotificationRow row = (ExpandableNotificationRow) v;
@@ -1766,7 +1722,6 @@
         return false;
     }
 
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private ExpandableView getChildAtPosition(float touchX, float touchY) {
         return getChildAtPosition(
                 touchX, touchY, true /* requireMinHeight */, true /* ignoreDecors */);
@@ -1781,7 +1736,6 @@
      * @param ignoreDecors     Whether decors can be returned
      * @return the child at the given location.
      */
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     ExpandableView getChildAtPosition(float touchX, float touchY,
                                       boolean requireMinHeight, boolean ignoreDecors) {
         // find the view under the pointer, accounting for GONE views
@@ -1829,12 +1783,10 @@
         return getChildAtPosition(touchX - mTempInt2[0], touchY - mTempInt2[1]);
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setScrollingEnabled(boolean enable) {
         mScrollingEnabled = enable;
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void lockScrollTo(View v) {
         if (mForcedScroll == v) {
             return;
@@ -1847,7 +1799,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public boolean scrollTo(View v) {
         ExpandableView expandableView = (ExpandableView) v;
         int positionInLinearLayout = getPositionInLinearLayout(v);
@@ -1869,7 +1820,6 @@
      * @return the scroll necessary to make the bottom edge of {@param v} align with the top of
      * the IME.
      */
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private int targetScrollForView(ExpandableView v, int positionInLinearLayout) {
         return positionInLinearLayout + v.getIntrinsicHeight() +
                 getImeInset() - getHeight()
@@ -1890,7 +1840,6 @@
     }
 
     @Override
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
         if (!mAnimatedInsets) {
             mBottomInset = insets.getInsets(WindowInsets.Type.ime()).bottom;
@@ -1920,7 +1869,6 @@
         return insets;
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private final Runnable mReclamp = new Runnable() {
         @Override
         public void run() {
@@ -1932,23 +1880,19 @@
         }
     };
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setExpandingEnabled(boolean enable) {
         mExpandHelper.setEnabled(enable);
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     private boolean isScrollingEnabled() {
         return mScrollingEnabled;
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     boolean onKeyguard() {
         return mStatusBarState == StatusBarState.KEYGUARD;
     }
 
     @Override
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
         Resources res = getResources();
@@ -1961,7 +1905,6 @@
         reinitView();
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     public void dismissViewAnimated(
             View child, Consumer<Boolean> endRunnable, int delay, long duration) {
         if (child instanceof SectionHeaderView) {
@@ -1979,7 +1922,6 @@
                 true /* isClearAll */);
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void snapViewIfNeeded(NotificationEntry entry) {
         ExpandableNotificationRow child = entry.getRow();
         boolean animate = mIsExpanded || isPinnedHeadsUp(child);
@@ -1990,7 +1932,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.ADAPTER)
     public ViewGroup getViewParentForNotification(NotificationEntry entry) {
         return this;
     }
@@ -2002,7 +1943,6 @@
      * @return The amount of scrolling to be performed by the scroller,
      * not handled by the overScroll amount.
      */
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     private float overScrollUp(int deltaY, int range) {
         deltaY = Math.max(deltaY, 0);
         float currentTopAmount = getCurrentOverScrollAmount(true);
@@ -2036,7 +1976,6 @@
      * @return The amount of scrolling to be performed by the scroller,
      * not handled by the overScroll amount.
      */
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     private float overScrollDown(int deltaY) {
         deltaY = Math.min(deltaY, 0);
         float currentBottomAmount = getCurrentOverScrollAmount(false);
@@ -2061,14 +2000,12 @@
         return scrollAmount;
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void initVelocityTrackerIfNotExists() {
         if (mVelocityTracker == null) {
             mVelocityTracker = VelocityTracker.obtain();
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void recycleVelocityTracker() {
         if (mVelocityTracker != null) {
             mVelocityTracker.recycle();
@@ -2076,7 +2013,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void initOrResetVelocityTracker() {
         if (mVelocityTracker == null) {
             mVelocityTracker = VelocityTracker.obtain();
@@ -2085,12 +2021,10 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setFinishScrollingCallback(Runnable runnable) {
         mFinishScrollingCallback = runnable;
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void animateScroll() {
         if (mScroller.computeScrollOffset()) {
             int oldY = mOwnScrollY;
@@ -2139,7 +2073,6 @@
      * @param scrollRangeY   The maximum allowable scroll position (absolute scrolling only).
      * @param maxOverScrollY The current (unsigned) limit on number of pixels to overscroll by.
      */
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void customOverScrollBy(int deltaY, int scrollY, int scrollRangeY, int maxOverScrollY) {
         int newScrollY = scrollY + deltaY;
         final int top = -maxOverScrollY;
@@ -2167,7 +2100,6 @@
      * @param onTop     Should the effect be applied on top of the scroller.
      * @param animate   Should an animation be performed.
      */
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     public void setOverScrolledPixels(float numPixels, boolean onTop, boolean animate) {
         setOverScrollAmount(numPixels * getRubberBandFactor(onTop), onTop, animate, true);
     }
@@ -2181,7 +2113,6 @@
      * @param animate Should an animation be performed.
      */
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     public void setOverScrollAmount(float amount, boolean onTop, boolean animate) {
         setOverScrollAmount(amount, onTop, animate, true);
     }
@@ -2194,7 +2125,6 @@
      * @param animate         Should an animation be performed.
      * @param cancelAnimators Should running animations be cancelled.
      */
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
                                     boolean cancelAnimators) {
         setOverScrollAmount(amount, onTop, animate, cancelAnimators, isRubberbanded(onTop));
@@ -2210,7 +2140,6 @@
      * @param isRubberbanded  The value which will be passed to
      *                        {@link OnOverscrollTopChangedListener#onOverscrollTopChanged}
      */
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
                                     boolean cancelAnimators, boolean isRubberbanded) {
         if (cancelAnimators) {
@@ -2219,7 +2148,6 @@
         setOverScrollAmountInternal(amount, onTop, animate, isRubberbanded);
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void setOverScrollAmountInternal(float amount, boolean onTop, boolean animate,
                                              boolean isRubberbanded) {
         amount = Math.max(0, amount);
@@ -2236,7 +2164,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private void notifyOverscrollTopListener(float amount, boolean isRubberbanded) {
         mExpandHelper.onlyObserveMovements(amount > 1.0f);
         if (mDontReportNextOverScroll) {
@@ -2248,23 +2175,19 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     public void setOverscrollTopChangedListener(
             OnOverscrollTopChangedListener overscrollTopChangedListener) {
         mOverscrollTopChangedListener = overscrollTopChangedListener;
     }
 
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     public float getCurrentOverScrollAmount(boolean top) {
         return mAmbientState.getOverScrollAmount(top);
     }
 
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     public float getCurrentOverScrolledPixels(boolean top) {
         return top ? mOverScrolledTopPixels : mOverScrolledBottomPixels;
     }
 
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private void setOverScrolledPixels(float amount, boolean onTop) {
         if (onTop) {
             mOverScrolledTopPixels = amount;
@@ -2281,7 +2204,6 @@
      * @param clampedY Whether this value was clamped by the calling method, meaning we've reached
      *                 the overscroll limit.
      */
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private void onCustomOverScrolled(int scrollY, boolean clampedY) {
         // Treat animating scrolls differently; see #computeScroll() for why.
         if (!mScroller.isFinished()) {
@@ -2305,7 +2227,6 @@
      * Springs back from an overscroll by stopping the {@link #mScroller} and animating the
      * overscroll amount back to zero.
      */
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void springBack() {
         int scrollRange = getScrollRange();
         boolean overScrolledTop = mOwnScrollY <= 0;
@@ -2329,7 +2250,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private int getScrollRange() {
         // In current design, it only use the top HUN to treat all of HUNs
         // although there are more than one HUNs
@@ -2346,7 +2266,6 @@
         return scrollRange;
     }
 
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private int getImeInset() {
         // The NotificationStackScrollLayout does not extend all the way to the bottom of the
         // display. Therefore, subtract that space from the mBottomInset, in order to only include
@@ -2358,7 +2277,6 @@
     /**
      * @return the first child which has visibility unequal to GONE
      */
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public ExpandableView getFirstChildNotGone() {
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
@@ -2374,7 +2292,6 @@
      * @return The first child which has visibility unequal to GONE which is currently below the
      * given translationY or equal to it.
      */
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private View getFirstChildBelowTranlsationY(float translationY, boolean ignoreChildren) {
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
@@ -2406,7 +2323,6 @@
     /**
      * @return the last child which has visibility unequal to GONE
      */
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     public ExpandableView getLastChildNotGone() {
         int childCount = getChildCount();
         for (int i = childCount - 1; i >= 0; i--) {
@@ -2432,7 +2348,6 @@
     /**
      * @return the number of children which have visibility unequal to GONE
      */
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     public int getNotGoneChildCount() {
         int childCount = getChildCount();
         int count = 0;
@@ -2445,7 +2360,6 @@
         return count;
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void updateContentHeight() {
         final float scrimTopPadding = mAmbientState.isOnKeyguard() ? 0 : mMinimumPaddings;
         final int shelfIntrinsicHeight = mShelf != null ? mShelf.getIntrinsicHeight() : 0;
@@ -2481,12 +2395,10 @@
                 previous, mAmbientState.getFractionToShade(), mAmbientState.isOnKeyguard());
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public boolean hasPulsingNotifications() {
         return mPulsing;
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     private void updateScrollability() {
         boolean scrollable = !mQsFullScreen && getScrollRange() > 0;
         if (scrollable != mScrollable) {
@@ -2496,7 +2408,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     private void updateForwardAndBackwardScrollability() {
         boolean forwardScrollable = mScrollable && !mScrollAdapter.isScrolledToBottom();
         boolean backwardsScrollable = mScrollable && !mScrollAdapter.isScrolledToTop();
@@ -2509,7 +2420,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     private void updateBackground() {
         // No need to update the background color if it's not being drawn.
         if (!mShouldDrawNotificationBackground) {
@@ -2540,7 +2450,6 @@
         mAnimateNextSectionBoundsChange = false;
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void abortBackgroundAnimators() {
         for (NotificationSection section : mSections) {
             section.cancelAnimators();
@@ -2556,7 +2465,6 @@
         return false;
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private boolean areSectionBoundsAnimating() {
         for (NotificationSection section : mSections) {
             if (section.areBoundsAnimating()) {
@@ -2566,7 +2474,6 @@
         return false;
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void startBackgroundAnimation() {
         // TODO(kprevas): do we still need separate fields for top/bottom?
         // or can each section manage its own animation state?
@@ -2586,7 +2493,6 @@
     /**
      * Update the background bounds to the new desired bounds
      */
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     private void updateBackgroundBounds() {
         int left = mSidePaddings;
         int right = getWidth() - mSidePaddings;
@@ -2652,7 +2558,6 @@
         return null;
     }
 
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private ExpandableView getLastChildWithBackground() {
         int childCount = getChildCount();
         for (int i = childCount - 1; i >= 0; i--) {
@@ -2665,7 +2570,6 @@
         return null;
     }
 
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private ExpandableView getFirstChildWithBackground() {
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
@@ -2700,7 +2604,6 @@
      *                  numbers mean that the finger/cursor is moving down the screen,
      *                  which means we want to scroll towards the top.
      */
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     protected void fling(int velocityY) {
         if (getChildCount() > 0) {
             float topAmount = getCurrentOverScrollAmount(true);
@@ -2739,7 +2642,6 @@
      * @return Whether a fling performed on the top overscroll edge lead to the expanded
      * overScroll view (i.e QS).
      */
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     private boolean shouldOverScrollFling(int initialVelocity) {
         float topOverScroll = getCurrentOverScrollAmount(true);
         return mScrolledToTopOnFirstDown
@@ -2756,7 +2658,6 @@
      * @param qsHeight the top padding imposed by the quick settings panel
      * @param animate  whether to animate the change
      */
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     public void updateTopPadding(float qsHeight, boolean animate) {
         int topPadding = (int) qsHeight;
         int minStackHeight = getLayoutMinHeight();
@@ -2769,12 +2670,10 @@
         setExpandedHeight(mExpandedHeight);
     }
 
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     public void setMaxTopPadding(int maxTopPadding) {
         mMaxTopPadding = maxTopPadding;
     }
 
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     public int getLayoutMinHeight() {
         if (isHeadsUpTransition()) {
             ExpandableNotificationRow trackedHeadsUpRow = mAmbientState.getTrackedHeadsUpRow();
@@ -2791,17 +2690,14 @@
         return mShelf.getVisibility() == GONE ? 0 : mShelf.getIntrinsicHeight();
     }
 
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     public float getTopPaddingOverflow() {
         return mTopPaddingOverflow;
     }
 
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private int clampPadding(int desiredPadding) {
         return Math.max(desiredPadding, mIntrinsicPadding);
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     private float getRubberBandFactor(boolean onTop) {
         if (!onTop) {
             return RUBBER_BAND_FACTOR_NORMAL;
@@ -2821,14 +2717,12 @@
      * rubberbanded, false if it is technically an overscroll but rather a motion to expand the
      * overscroll view (e.g. expand QS).
      */
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     private boolean isRubberbanded(boolean onTop) {
         return !onTop || mExpandedInThisMotion || mIsExpansionChanging || mPanelTracking
                 || !mScrolledToTopOnFirstDown;
     }
 
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setChildTransferInProgress(boolean childTransferInProgress) {
         Assert.isMainThread();
         mChildTransferInProgress = childTransferInProgress;
@@ -2843,7 +2737,6 @@
         mOnNotificationRemovedListener = listener;
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     @Override
     public void onViewRemoved(View child) {
         super.onViewRemoved(child);
@@ -2864,7 +2757,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     public void cleanUpViewStateForEntry(NotificationEntry entry) {
         View child = entry.getRow();
         if (child == mSwipeHelper.getTranslatingParentView()) {
@@ -2872,7 +2764,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private void onViewRemovedInternal(ExpandableView child, ViewGroup container) {
         if (mChangePositionInProgress) {
             // This is only a position change, don't do anything special
@@ -2909,7 +2800,6 @@
         return Math.abs(child.getTranslation()) >= Math.abs(getTotalTranslationLength(child));
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void focusNextViewIfFocused(View view) {
         if (view instanceof ExpandableNotificationRow) {
             ExpandableNotificationRow row = (ExpandableNotificationRow) view;
@@ -2929,7 +2819,6 @@
 
     }
 
-    @ShadeViewRefactor(RefactorComponent.ADAPTER)
     private boolean isChildInGroup(View child) {
         return child instanceof ExpandableNotificationRow
                 && mGroupMembershipManager.isChildInGroup(
@@ -2942,7 +2831,6 @@
      * @param child The view to generate the remove animation for.
      * @return Whether an animation was generated.
      */
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     boolean generateRemoveAnimation(ExpandableView child) {
         String key = "";
         if (mDebugRemoveAnimation) {
@@ -2986,7 +2874,6 @@
         return false;
     }
 
-    @ShadeViewRefactor(RefactorComponent.ADAPTER)
     private boolean isClickedHeadsUp(View child) {
         return HeadsUpUtil.isClickedHeadsUpNotification(child);
     }
@@ -2996,7 +2883,6 @@
      *
      * @return whether any child was removed from the list to animate and the view was just added
      */
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private boolean removeRemovedChildFromHeadsUpChangeAnimations(View child) {
         boolean hasAddEvent = false;
         for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
@@ -3021,7 +2907,6 @@
      *
      * @param removedChild the removed child
      */
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void updateScrollStateForRemovedChild(ExpandableView removedChild) {
         final int startingPosition = getPositionInLinearLayout(removedChild);
         final int childHeight = getIntrinsicHeight(removedChild) + mPaddingBetweenElements;
@@ -3050,7 +2935,6 @@
         return mTopPadding - mQsScrollBoundaryPosition;
     }
 
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private int getIntrinsicHeight(View view) {
         if (view instanceof ExpandableView) {
             ExpandableView expandableView = (ExpandableView) view;
@@ -3059,7 +2943,6 @@
         return view.getHeight();
     }
 
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     public int getPositionInLinearLayout(View requestedView) {
         ExpandableNotificationRow childInGroup = null;
         ExpandableNotificationRow requestedRow = null;
@@ -3100,7 +2983,6 @@
     }
 
     @Override
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void onViewAdded(View child) {
         super.onViewAdded(child);
         if (child instanceof ExpandableView) {
@@ -3108,7 +2990,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void updateFirstAndLastBackgroundViews() {
         NotificationSection firstSection = getFirstVisibleSection();
         NotificationSection lastSection = getLastVisibleSection();
@@ -3136,7 +3017,6 @@
         invalidate();
     }
 
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private void onViewAddedInternal(ExpandableView child) {
         updateHideSensitiveForChild(child);
         child.setOnHeightChangedListener(mOnChildHeightChangedListener);
@@ -3154,12 +3034,10 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private void updateHideSensitiveForChild(ExpandableView child) {
         child.setHideSensitiveForIntrinsicHeight(mAmbientState.isHideSensitive());
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void notifyGroupChildRemoved(ExpandableView row, ViewGroup childrenContainer) {
         onViewRemovedInternal(row, childrenContainer);
     }
@@ -3168,7 +3046,6 @@
         onViewAddedInternal(row);
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     public void setAnimationsEnabled(boolean animationsEnabled) {
         mAnimationsEnabled = animationsEnabled;
         updateNotificationAnimationStates();
@@ -3179,7 +3056,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void updateNotificationAnimationStates() {
         boolean running = mAnimationsEnabled || hasPulsingNotifications();
         mShelf.setAnimationsEnabled(running);
@@ -3191,13 +3067,11 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     void updateAnimationState(View child) {
         updateAnimationState((mAnimationsEnabled || hasPulsingNotifications())
                 && (mIsExpanded || isPinnedHeadsUp(child)), child);
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     void setExpandingNotification(ExpandableNotificationRow row) {
         if (mExpandingNotificationRow != null && row == null) {
             // Let's unset the clip path being set during launch
@@ -3216,7 +3090,6 @@
         return v.getParent() == this;
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     public void applyLaunchAnimationParams(LaunchAnimationParameters params) {
         // Modify the clipping for launching notifications
         mLaunchAnimationParams = params;
@@ -3225,7 +3098,6 @@
         requestChildrenUpdate();
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void updateAnimationState(boolean running, View child) {
         if (child instanceof ExpandableNotificationRow) {
             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
@@ -3233,13 +3105,11 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     boolean isAddOrRemoveAnimationPending() {
         return mNeedsAnimation
                 && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty());
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     public void generateAddAnimation(ExpandableView child, boolean fromMoreCard) {
         if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress && !isFullyHidden()) {
             // Generate Animations
@@ -3256,7 +3126,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     public void changeViewPosition(ExpandableView child, int newIndex) {
         Assert.isMainThread();
         if (mChangePositionInProgress) {
@@ -3290,7 +3159,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void startAnimationToState() {
         if (mNeedsAnimation) {
             generateAllAnimationEvents();
@@ -3308,7 +3176,6 @@
         mGoToFullShadeDelay = 0;
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void generateAllAnimationEvents() {
         generateHeadsUpAnimationEvents();
         generateChildRemovalEvents();
@@ -3324,7 +3191,6 @@
         generateAnimateEverythingEvent();
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void generateHeadsUpAnimationEvents() {
         for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
             ExpandableNotificationRow row = eventPair.first;
@@ -3388,13 +3254,11 @@
         mAddedHeadsUpChildren.clear();
     }
 
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private boolean shouldHunAppearFromBottom(ExpandableViewState viewState) {
         return viewState.getYTranslation() + viewState.height
                 >= mAmbientState.getMaxHeadsUpTranslation();
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void generateGroupExpansionEvent() {
         // Generate a group expansion/collapsing event if there is such a group at all
         if (mExpandedGroupView != null) {
@@ -3404,7 +3268,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void generateViewResizeEvent() {
         if (mNeedViewResizeAnimation) {
             boolean hasDisappearAnimation = false;
@@ -3425,7 +3288,6 @@
         mNeedViewResizeAnimation = false;
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void generateChildRemovalEvents() {
         for (ExpandableView child : mChildrenToRemoveAnimated) {
             boolean childWasSwipedOut = mSwipedOutViews.contains(child);
@@ -3473,7 +3335,6 @@
         mChildrenToRemoveAnimated.clear();
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void generatePositionChangeEvents() {
         for (ExpandableView child : mChildrenChangingPositions) {
             Integer duration = null;
@@ -3498,7 +3359,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void generateChildAdditionEvents() {
         for (ExpandableView child : mChildrenToAddAnimated) {
             if (mFromMoreCardAdditions.contains(child)) {
@@ -3514,7 +3374,6 @@
         mFromMoreCardAdditions.clear();
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void generateTopPaddingEvent() {
         if (mTopPaddingNeedsAnimation) {
             AnimationEvent event;
@@ -3531,7 +3390,6 @@
         mTopPaddingNeedsAnimation = false;
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void generateActivateEvent() {
         if (mActivateNeedsAnimation) {
             mAnimationEvents.add(
@@ -3540,7 +3398,6 @@
         mActivateNeedsAnimation = false;
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void generateAnimateEverythingEvent() {
         if (mEverythingNeedsAnimation) {
             mAnimationEvents.add(
@@ -3549,7 +3406,6 @@
         mEverythingNeedsAnimation = false;
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void generateDimmedEvent() {
         if (mDimmedNeedsAnimation) {
             mAnimationEvents.add(
@@ -3558,7 +3414,6 @@
         mDimmedNeedsAnimation = false;
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void generateHideSensitiveEvent() {
         if (mHideSensitiveNeedsAnimation) {
             mAnimationEvents.add(
@@ -3567,7 +3422,6 @@
         mHideSensitiveNeedsAnimation = false;
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void generateGoToFullShadeEvent() {
         if (mGoToFullShadeNeedsAnimation) {
             mAnimationEvents.add(
@@ -3576,7 +3430,6 @@
         mGoToFullShadeNeedsAnimation = false;
     }
 
-    @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM)
     protected StackScrollAlgorithm createStackScrollAlgorithm(Context context) {
         return new StackScrollAlgorithm(context, this);
     }
@@ -3584,7 +3437,6 @@
     /**
      * @return Whether a y coordinate is inside the content.
      */
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public boolean isInContentBounds(float y) {
         return y < getHeight() - getEmptyBottomMargin();
     }
@@ -3605,7 +3457,6 @@
         return super.onTouchEvent(ev);
     }
 
-    @ShadeViewRefactor(RefactorComponent.INPUT)
     void dispatchDownEventToScroller(MotionEvent ev) {
         MotionEvent downEvent = MotionEvent.obtain(ev);
         downEvent.setAction(MotionEvent.ACTION_DOWN);
@@ -3614,7 +3465,6 @@
     }
 
     @Override
-    @ShadeViewRefactor(RefactorComponent.INPUT)
     public boolean onGenericMotionEvent(MotionEvent event) {
         if (!isScrollingEnabled()
                 || !mIsExpanded
@@ -3650,7 +3500,6 @@
         return super.onGenericMotionEvent(event);
     }
 
-    @ShadeViewRefactor(RefactorComponent.INPUT)
     boolean onScrollTouch(MotionEvent ev) {
         if (!isScrollingEnabled()) {
             return false;
@@ -3807,7 +3656,6 @@
         return mFlingAfterUpEvent;
     }
 
-    @ShadeViewRefactor(RefactorComponent.INPUT)
     protected boolean isInsideQsHeader(MotionEvent ev) {
         mQsHeader.getBoundsOnScreen(mQsHeaderBound);
         /**
@@ -3825,7 +3673,6 @@
         return mQsHeaderBound.contains((int) ev.getRawX(), (int) ev.getRawY());
     }
 
-    @ShadeViewRefactor(RefactorComponent.INPUT)
     private void onOverScrollFling(boolean open, int initialVelocity) {
         if (mOverscrollTopChangedListener != null) {
             mOverscrollTopChangedListener.flingTopOverscroll(initialVelocity, open);
@@ -3835,7 +3682,6 @@
     }
 
 
-    @ShadeViewRefactor(RefactorComponent.INPUT)
     private void onSecondaryPointerUp(MotionEvent ev) {
         final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
                 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
@@ -3853,7 +3699,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.INPUT)
     private void endDrag() {
         setIsBeingDragged(false);
 
@@ -3868,7 +3713,6 @@
     }
 
     @Override
-    @ShadeViewRefactor(RefactorComponent.INPUT)
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         if (mTouchHandler != null && mTouchHandler.onInterceptTouchEvent(ev)) {
             return true;
@@ -3876,7 +3720,6 @@
         return super.onInterceptTouchEvent(ev);
     }
 
-    @ShadeViewRefactor(RefactorComponent.INPUT)
     void handleEmptySpaceClick(MotionEvent ev) {
         logEmptySpaceClick(ev, isBelowLastNotification(mInitialTouchX, mInitialTouchY),
                 mStatusBarState, mTouchIsClick);
@@ -3919,7 +3762,6 @@
                 MotionEvent.actionToString(ev.getActionMasked()));
     }
 
-    @ShadeViewRefactor(RefactorComponent.INPUT)
     void initDownStates(MotionEvent ev) {
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
             mExpandedInThisMotion = false;
@@ -3933,7 +3775,6 @@
     }
 
     @Override
-    @ShadeViewRefactor(RefactorComponent.INPUT)
     public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
         super.requestDisallowInterceptTouchEvent(disallowIntercept);
         if (disallowIntercept) {
@@ -3941,7 +3782,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.INPUT)
     boolean onInterceptTouchEventScroll(MotionEvent ev) {
         if (!isScrollingEnabled()) {
             return false;
@@ -4056,14 +3896,12 @@
     /**
      * @return Whether the specified motion event is actually happening over the content.
      */
-    @ShadeViewRefactor(RefactorComponent.INPUT)
     private boolean isInContentBounds(MotionEvent event) {
         return isInContentBounds(event.getY());
     }
 
 
     @VisibleForTesting
-    @ShadeViewRefactor(RefactorComponent.INPUT)
     void setIsBeingDragged(boolean isDragged) {
         mIsBeingDragged = isDragged;
         if (isDragged) {
@@ -4073,22 +3911,18 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.INPUT)
     public void requestDisallowLongPress() {
         cancelLongPress();
     }
 
-    @ShadeViewRefactor(RefactorComponent.INPUT)
     public void requestDisallowDismiss() {
         mDisallowDismissInThisMotion = true;
     }
 
-    @ShadeViewRefactor(RefactorComponent.INPUT)
     public void cancelLongPress() {
         mSwipeHelper.cancelLongPress();
     }
 
-    @ShadeViewRefactor(RefactorComponent.INPUT)
     public void setOnEmptySpaceClickListener(OnEmptySpaceClickListener listener) {
         mOnEmptySpaceClickListener = listener;
     }
@@ -4097,7 +3931,6 @@
      * @hide
      */
     @Override
-    @ShadeViewRefactor(RefactorComponent.INPUT)
     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
         if (super.performAccessibilityActionInternal(action, arguments)) {
             return true;
@@ -4132,7 +3965,6 @@
     }
 
     @Override
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void onWindowFocusChanged(boolean hasWindowFocus) {
         super.onWindowFocusChanged(hasWindowFocus);
         if (!hasWindowFocus) {
@@ -4141,7 +3973,6 @@
     }
 
     @Override
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void clearChildFocus(View child) {
         super.clearChildFocus(child);
         if (mForcedScroll == child) {
@@ -4153,7 +3984,6 @@
         return mScrollAdapter.isScrolledToBottom();
     }
 
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     int getEmptyBottomMargin() {
         int contentHeight;
         if (mShouldUseSplitNotificationShade) {
@@ -4168,13 +3998,11 @@
         return Math.max(mMaxLayoutHeight - contentHeight, 0);
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     void onExpansionStarted() {
         mIsExpansionChanging = true;
         mAmbientState.setExpansionChanging(true);
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     void onExpansionStopped() {
         mIsExpansionChanging = false;
         mAmbientState.setExpansionChanging(false);
@@ -4187,7 +4015,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void clearUserLockedViews() {
         for (int i = 0; i < getChildCount(); i++) {
             ExpandableView child = getChildAtIndex(i);
@@ -4198,7 +4025,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void clearTemporaryViews() {
         // lets make sure nothing is transient anymore
         clearTemporaryViewsInGroup(this);
@@ -4211,7 +4037,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void clearTemporaryViewsInGroup(ViewGroup viewGroup) {
         while (viewGroup != null && viewGroup.getTransientViewCount() != 0) {
             final View transientView = viewGroup.getTransientView(0);
@@ -4222,27 +4047,23 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     void onPanelTrackingStarted() {
         mPanelTracking = true;
         mAmbientState.setPanelTracking(true);
         resetExposedMenuView(true /* animate */, true /* force */);
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     void onPanelTrackingStopped() {
         mPanelTracking = false;
         mAmbientState.setPanelTracking(false);
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     void resetScrollPosition() {
         mScroller.abortAnimation();
         setOwnScrollY(0);
     }
 
     @VisibleForTesting
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     void setIsExpanded(boolean isExpanded) {
         boolean changed = isExpanded != mIsExpanded;
         mIsExpanded = isExpanded;
@@ -4267,7 +4088,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private void updateChronometers() {
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
@@ -4275,7 +4095,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     void updateChronometerForChild(View child) {
         if (child instanceof ExpandableNotificationRow) {
             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
@@ -4316,7 +4135,6 @@
         updateChronometerForChild(view);
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void updateScrollPositionOnExpandInBottom(ExpandableView view) {
         if (view instanceof ExpandableNotificationRow && !onKeyguard()) {
             ExpandableNotificationRow row = (ExpandableNotificationRow) view;
@@ -4345,13 +4163,11 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     void setOnHeightChangedListener(
             ExpandableView.OnHeightChangedListener onHeightChangedListener) {
         this.mOnHeightChangedListener = onHeightChangedListener;
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     void onChildAnimationFinished() {
         setAnimationRunning(false);
         requestChildrenUpdate();
@@ -4372,7 +4188,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void clearHeadsUpDisappearRunning() {
         for (int i = 0; i < getChildCount(); i++) {
             View view = getChildAt(i);
@@ -4388,7 +4203,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void clearTransient() {
         for (ExpandableView view : mClearTransientViewsWhenFinished) {
             view.removeFromTransientContainer();
@@ -4396,7 +4210,6 @@
         mClearTransientViewsWhenFinished.clear();
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void runAnimationFinishedRunnables() {
         for (Runnable runnable : mAnimationFinishedRunnables) {
             runnable.run();
@@ -4407,7 +4220,6 @@
     /**
      * See {@link AmbientState#setDimmed}.
      */
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     void setDimmed(boolean dimmed, boolean animate) {
         dimmed &= onKeyguard();
         mAmbientState.setDimmed(dimmed);
@@ -4422,18 +4234,15 @@
     }
 
     @VisibleForTesting
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     boolean isDimmed() {
         return mAmbientState.isDimmed();
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     private void setDimAmount(float dimAmount) {
         mDimAmount = dimAmount;
         updateBackgroundDimming();
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void animateDimmed(boolean dimmed) {
         if (mDimAnimator != null) {
             mDimAnimator.cancel();
@@ -4450,7 +4259,6 @@
         mDimAnimator.start();
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     void updateSensitiveness(boolean animate, boolean hideSensitive) {
         if (hideSensitive != mAmbientState.isHideSensitive()) {
             int childCount = getChildCount();
@@ -4468,7 +4276,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void applyCurrentState() {
         int numChildren = getChildCount();
         for (int i = 0; i < numChildren; i++) {
@@ -4485,7 +4292,6 @@
         updateViewShadows();
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void updateViewShadows() {
         // we need to work around an issue where the shadow would not cast between siblings when
         // their z difference is between 0 and 0.1
@@ -4526,7 +4332,6 @@
     /**
      * Update colors of "dismiss" and "empty shade" views.
      */
-    @ShadeViewRefactor(RefactorComponent.DECORATOR)
     void updateDecorViews() {
         final @ColorInt int textColor =
                 Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorPrimary);
@@ -4535,7 +4340,6 @@
         mEmptyShadeView.setTextColor(textColor);
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     void goToFullShade(long delay) {
         mGoToFullShadeNeedsAnimation = true;
         mGoToFullShadeDelay = delay;
@@ -4543,23 +4347,19 @@
         requestChildrenUpdate();
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void cancelExpandHelper() {
         mExpandHelper.cancel();
     }
 
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     void setIntrinsicPadding(int intrinsicPadding) {
         mIntrinsicPadding = intrinsicPadding;
     }
 
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     int getIntrinsicPadding() {
         return mIntrinsicPadding;
     }
 
     @Override
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public boolean shouldDelayChildPressedState() {
         return true;
     }
@@ -4567,7 +4367,6 @@
     /**
      * See {@link AmbientState#setDozing}.
      */
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setDozing(boolean dozing, boolean animate) {
         if (mAmbientState.isDozing() == dozing) {
             return;
@@ -4586,7 +4385,6 @@
      * @param interpolatedHideAmount The hide amount that follows the actual interpolation of the
      *                               animation curve.
      */
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     void setHideAmount(float linearHideAmount, float interpolatedHideAmount) {
         mLinearHideAmount = linearHideAmount;
         mInterpolatedHideAmount = interpolatedHideAmount;
@@ -4627,7 +4425,6 @@
         mController.updateVisibility(!mAmbientState.isFullyHidden() || !onKeyguard());
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     void notifyHideAnimationStart(boolean hide) {
         // We only swap the scaling factor if we're fully hidden or fully awake to avoid
         // interpolation issues when playing with the power button.
@@ -4639,7 +4436,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     private int getNotGoneIndex(View child) {
         int count = getChildCount();
         int notGoneIndex = 0;
@@ -4663,7 +4459,6 @@
         return mFooterView != null && mFooterView.isHistoryShown();
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     void setFooterView(@NonNull FooterView footerView) {
         int index = -1;
         if (mFooterView != null) {
@@ -4677,7 +4472,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setEmptyShadeView(EmptyShadeView emptyShadeView) {
         int index = -1;
         if (mEmptyShadeView != null) {
@@ -4688,7 +4482,6 @@
         addView(mEmptyShadeView, index);
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     void updateEmptyShadeView(boolean visible, boolean areNotificationsHiddenInShade) {
         mEmptyShadeView.setVisible(visible, mIsExpanded && mAnimationsEnabled);
 
@@ -4731,7 +4524,6 @@
         return mEmptyShadeView.isVisible();
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void updateFooterView(boolean visible, boolean showDismissView, boolean showHistory) {
         if (mFooterView == null || mNotificationStackSizeCalculator == null) {
             return;
@@ -4743,7 +4535,6 @@
         mFooterView.setFooterLabelVisible(mHasFilteredOutSeenNotifications);
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setClearAllInProgress(boolean clearAllInProgress) {
         mClearAllInProgress = clearAllInProgress;
         mAmbientState.setClearAllInProgress(clearAllInProgress);
@@ -4754,19 +4545,16 @@
         return mClearAllInProgress;
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public boolean isFooterViewNotGone() {
         return mFooterView != null
                 && mFooterView.getVisibility() != View.GONE
                 && !mFooterView.willBeGone();
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public boolean isFooterViewContentVisible() {
         return mFooterView != null && mFooterView.isContentVisible();
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public int getFooterViewHeightWithPadding() {
         return mFooterView == null ? 0 : mFooterView.getHeight()
                 + mPaddingBetweenElements
@@ -4780,12 +4568,10 @@
         return mGapHeight + mPaddingBetweenElements;
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public int getEmptyShadeViewHeight() {
         return mEmptyShadeView.getHeight();
     }
 
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     public float getBottomMostNotificationBottom() {
         final int count = getChildCount();
         float max = 0;
@@ -4803,7 +4589,6 @@
         return max + getStackTranslation();
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setCentralSurfaces(CentralSurfaces centralSurfaces) {
         this.mCentralSurfaces = centralSurfaces;
     }
@@ -4812,7 +4597,6 @@
         mActivityStarter = activityStarter;
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     void requestAnimateEverything() {
         if (mIsExpanded && mAnimationsEnabled) {
             mEverythingNeedsAnimation = true;
@@ -4821,7 +4605,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     public boolean isBelowLastNotification(float touchX, float touchY) {
         int childCount = getChildCount();
         for (int i = childCount - 1; i >= 0; i--) {
@@ -4856,7 +4639,6 @@
      * @hide
      */
     @Override
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
         super.onInitializeAccessibilityEventInternal(event);
         event.setScrollable(mScrollable);
@@ -4866,7 +4648,6 @@
     }
 
     @Override
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfoInternal(info);
         if (mScrollable) {
@@ -4885,7 +4666,6 @@
         info.setClassName(ScrollView.class.getName());
     }
 
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     public void generateChildOrderChangedEvent() {
         if (mIsExpanded && mAnimationsEnabled) {
             mGenerateChildOrderChangedEvent = true;
@@ -4894,17 +4674,14 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public int getContainerChildCount() {
         return getChildCount();
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public View getContainerChildAt(int i) {
         return getChildAt(i);
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void removeContainerView(View v) {
         Assert.isMainThread();
         removeView(v);
@@ -4916,7 +4693,6 @@
         updateSpeedBumpIndex();
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void addContainerView(View v) {
         Assert.isMainThread();
         addView(v);
@@ -4950,7 +4726,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void runAfterAnimationFinished(Runnable runnable) {
         mAnimationFinishedRunnables.add(runnable);
     }
@@ -4960,7 +4735,6 @@
         generateHeadsUpAnimation(row, isHeadsUp);
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) {
         final boolean add = mAnimationsEnabled && (isHeadsUp || mHeadsUpGoingAwayAnimationsAllowed);
         if (SPEW) {
@@ -4995,7 +4769,6 @@
      * @param height          the height of the screen
      * @param bottomBarHeight the height of the bar on the bottom
      */
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setHeadsUpBoundaries(int height, int bottomBarHeight) {
         mAmbientState.setMaxHeadsUpTranslation(height - bottomBarHeight);
         mStateAnimator.setHeadsUpAppearHeightBottom(height);
@@ -5006,23 +4779,19 @@
         mWillExpand = willExpand;
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setTrackingHeadsUp(ExpandableNotificationRow row) {
         mAmbientState.setTrackedHeadsUpRow(row);
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void forceNoOverlappingRendering(boolean force) {
         mForceNoOverlappingRendering = force;
     }
 
     @Override
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public boolean hasOverlappingRendering() {
         return !mForceNoOverlappingRendering && super.hasOverlappingRendering();
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     public void setAnimationRunning(boolean animationRunning) {
         if (animationRunning != mAnimationRunning) {
             if (animationRunning) {
@@ -5035,12 +4804,10 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public boolean isExpanded() {
         return mIsExpanded;
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setPulsing(boolean pulsing, boolean animated) {
         if (!mPulsing && !pulsing) {
             return;
@@ -5055,7 +4822,6 @@
         notifyHeightChangeListener(null, animated);
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setQsFullScreen(boolean qsFullScreen) {
         mQsFullScreen = qsFullScreen;
         updateAlgorithmLayoutMinHeight();
@@ -5066,7 +4832,6 @@
         return mQsFullScreen;
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setQsExpansionFraction(float qsExpansionFraction) {
         boolean footerAffected = mQsExpansionFraction != qsExpansionFraction
                 && (mQsExpansionFraction == 1 || qsExpansionFraction == 1);
@@ -5084,12 +4849,10 @@
     }
 
     @VisibleForTesting
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     void setOwnScrollY(int ownScrollY) {
         setOwnScrollY(ownScrollY, false /* animateScrollChangeListener */);
     }
 
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     private void setOwnScrollY(int ownScrollY, boolean animateStackYChangeListener) {
         // Avoid Flicking during clear all
         // when the shade finishes closing, onExpansionStopped will call
@@ -5142,7 +4905,6 @@
         shelf.bind(mAmbientState, this, mController.getNotificationRoundnessManager());
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setShelfController(NotificationShelfController notificationShelfController) {
         NotificationShelfController.assertRefactorFlagDisabled(mAmbientState.getFeatureFlags());
         int index = -1;
@@ -5157,7 +4919,6 @@
         notificationShelfController.bind(mAmbientState, mController);
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setMaxDisplayedNotifications(int maxDisplayedNotifications) {
         if (mMaxDisplayedNotifications != maxDisplayedNotifications) {
             mMaxDisplayedNotifications = maxDisplayedNotifications;
@@ -5176,13 +4937,11 @@
         mKeyguardBottomPadding = keyguardBottomPadding;
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setShouldShowShelfOnly(boolean shouldShowShelfOnly) {
         mShouldShowShelfOnly = shouldShowShelfOnly;
         updateAlgorithmLayoutMinHeight();
     }
 
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     public int getMinExpansionHeight() {
         // shelf height is defined in dp but status bar height can be defined in px, that makes
         // relation between them variable - sometimes one might be bigger than the other when
@@ -5193,19 +4952,16 @@
                 + mWaterfallTopInset;
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setInHeadsUpPinnedMode(boolean inHeadsUpPinnedMode) {
         mInHeadsUpPinnedMode = inHeadsUpPinnedMode;
         updateClipping();
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
         mHeadsUpAnimatingAway = headsUpAnimatingAway;
         updateClipping();
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     @VisibleForTesting
     public void setStatusBarState(int statusBarState) {
         mStatusBarState = statusBarState;
@@ -5238,12 +4994,10 @@
         updateVisibility();
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setExpandingVelocity(float expandingVelocity) {
         mAmbientState.setExpandingVelocity(expandingVelocity);
     }
 
-    @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     public float getOpeningHeight() {
         if (mEmptyShadeView.getVisibility() == GONE) {
             return getMinExpansionHeight();
@@ -5252,12 +5006,10 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setIsFullWidth(boolean isFullWidth) {
         mAmbientState.setSmallScreen(isFullWidth);
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setUnlockHintRunning(boolean running) {
         mAmbientState.setUnlockHintRunning(running);
         if (!running) {
@@ -5266,7 +5018,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setPanelFlinging(boolean flinging) {
         mAmbientState.setFlinging(flinging);
         if (!flinging) {
@@ -5275,12 +5026,10 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setHeadsUpGoingAwayAnimationsAllowed(boolean headsUpGoingAwayAnimationsAllowed) {
         mHeadsUpGoingAwayAnimationsAllowed = headsUpGoingAwayAnimationsAllowed;
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void dump(PrintWriter pwOriginal, String[] args) {
         IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal);
         pw.println("Internal state:");
@@ -5376,7 +5125,6 @@
                 });
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public boolean isFullyHidden() {
         return mAmbientState.isFullyHidden();
     }
@@ -5387,7 +5135,6 @@
      *
      * @param listener the listener to notify.
      */
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void addOnExpandedHeightChangedListener(BiConsumer<Float, Float> listener) {
         mExpandedHeightListeners.add(listener);
     }
@@ -5395,12 +5142,10 @@
     /**
      * Stop a listener from listening to the expandedHeight.
      */
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void removeOnExpandedHeightChangedListener(BiConsumer<Float, Float> listener) {
         mExpandedHeightListeners.remove(listener);
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     void setHeadsUpAppearanceController(
             HeadsUpAppearanceController headsUpAppearanceController) {
         mHeadsUpAppearanceController = headsUpAppearanceController;
@@ -5492,7 +5237,6 @@
      * Collects a list of visible rows, and animates them away in a staggered fashion as if they
      * were dismissed. Notifications are dismissed in the backend via onClearAllAnimationsEnd.
      */
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     @VisibleForTesting
     void clearNotifications(@SelectedRows int selection, boolean closeShade) {
         // Animate-swipe all dismissable notifications, then animate the shade closed
@@ -5553,7 +5297,6 @@
     }
 
     @VisibleForTesting
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     protected void inflateFooterView() {
         FooterView footerView = (FooterView) LayoutInflater.from(mContext).inflate(
                 R.layout.status_bar_notification_footer, this, false);
@@ -5567,7 +5310,6 @@
         setFooterView(footerView);
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     private void inflateEmptyShadeView() {
         EmptyShadeView oldView = mEmptyShadeView;
         EmptyShadeView view = (EmptyShadeView) LayoutInflater.from(mContext).inflate(
@@ -5589,7 +5331,6 @@
     /**
      * Updates expanded, dimmed and locked states of notification rows.
      */
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     public void onUpdateRowStates() {
 
         // The following views will be moved to the end of mStackScroller. This counter represents
@@ -6061,7 +5802,6 @@
     /**
      * A listener that is notified when the empty space below the notifications is clicked on
      */
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public interface OnEmptySpaceClickListener {
         void onEmptySpaceClicked(float x, float y);
     }
@@ -6069,7 +5809,6 @@
     /**
      * A listener that gets notified when the overscroll at the top has changed.
      */
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public interface OnOverscrollTopChangedListener {
 
         /**
@@ -6093,7 +5832,6 @@
         void flingTopOverscroll(float velocity, boolean open);
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     private void updateSpeedBumpIndex() {
         mSpeedBumpIndexDirty = true;
     }
@@ -6129,7 +5867,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     private void resetExposedMenuView(boolean animate, boolean force) {
         mSwipeHelper.resetExposedMenuView(animate, force);
     }
@@ -6149,7 +5886,6 @@
         }
     }
 
-    @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     static class AnimationEvent {
 
         static AnimationFilter[] FILTERS = new AnimationFilter[]{
@@ -6438,7 +6174,6 @@
         setCheckForLeaveBehind(true);
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     private final HeadsUpTouchHelper.Callback mHeadsUpCallback = new HeadsUpTouchHelper.Callback() {
         @Override
         public ExpandableView getChildAtRawPosition(float touchX, float touchY) {
@@ -6477,7 +6212,6 @@
         });
     }
 
-    @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     private final ExpandHelper.Callback mExpandHelperCallback = new ExpandHelper.Callback() {
         @Override
         public ExpandableView getChildAtPosition(float touchX, float touchY) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index d9dc887..bbb4f24 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -236,7 +236,11 @@
 
     override fun postStartActivityDismissingKeyguard(intent: Intent, delay: Int) {
         postOnUiThread(delay) {
-            activityStarterInternal.startActivityDismissingKeyguard(intent = intent)
+            activityStarterInternal.startActivityDismissingKeyguard(
+                intent = intent,
+                onlyProvisioned = true,
+                dismissShade = true,
+            )
         }
     }
 
@@ -248,6 +252,8 @@
         postOnUiThread(delay) {
             activityStarterInternal.startActivityDismissingKeyguard(
                 intent = intent,
+                onlyProvisioned = true,
+                dismissShade = true,
                 animationController = animationController,
             )
         }
@@ -262,6 +268,8 @@
         postOnUiThread(delay) {
             activityStarterInternal.startActivityDismissingKeyguard(
                 intent = intent,
+                onlyProvisioned = true,
+                dismissShade = true,
                 animationController = animationController,
                 customMessage = customMessage,
             )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 6742e4f..bd7840d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -190,7 +190,6 @@
         }
     }
 
-    @VisibleForTesting
     public enum BiometricUiEvent implements UiEventLogger.UiEventEnum {
 
         @UiEvent(doc = "A biometric event of type fingerprint succeeded.")
@@ -221,7 +220,10 @@
         BIOMETRIC_IRIS_ERROR(404),
 
         @UiEvent(doc = "Bouncer was shown as a result of consecutive failed UDFPS attempts.")
-        BIOMETRIC_BOUNCER_SHOWN(916);
+        BIOMETRIC_BOUNCER_SHOWN(916),
+
+        @UiEvent(doc = "Screen started waking up with the given PowerManager wake reason.")
+        STARTED_WAKING_UP(1378);
 
         private final int mId;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index 618120d..7312db6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -20,6 +20,7 @@
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
 
 import android.annotation.NonNull;
+import android.graphics.Point;
 import android.os.Bundle;
 import android.os.PowerManager;
 import android.os.SystemClock;
@@ -38,6 +39,7 @@
 import com.android.systemui.doze.DozeReceiver;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.keyguard.domain.interactor.BurnInInteractor;
+import com.android.systemui.keyguard.domain.interactor.DozeInteractor;
 import com.android.systemui.shade.NotificationShadeWindowViewController;
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -99,6 +101,7 @@
     private CentralSurfaces mCentralSurfaces;
     private boolean mAlwaysOnSuppressed;
     private boolean mPulsePending;
+    private DozeInteractor mDozeInteractor;
 
     @Inject
     public DozeServiceHost(DozeLog dozeLog, PowerManager powerManager,
@@ -115,6 +118,7 @@
             NotificationWakeUpCoordinator notificationWakeUpCoordinator,
             AuthController authController,
             NotificationIconAreaController notificationIconAreaController,
+            DozeInteractor dozeInteractor,
             BurnInInteractor burnInInteractor) {
         super();
         mDozeLog = dozeLog;
@@ -136,6 +140,7 @@
         mNotificationIconAreaController = notificationIconAreaController;
         mBurnInInteractor = burnInInteractor;
         mHeadsUpManagerPhone.addListener(mOnHeadsUpChangedListener);
+        mDozeInteractor = dozeInteractor;
     }
 
     // TODO: we should try to not pass status bar in here if we can avoid it.
@@ -226,6 +231,7 @@
         for (Callback callback : mCallbacks) {
             callback.onDozingChanged(dozing);
         }
+        mDozeInteractor.setIsDozing(dozing);
         mStatusBarStateController.setIsDozing(dozing);
     }
 
@@ -360,7 +366,14 @@
 
     @Override
     public void onSlpiTap(float screenX, float screenY) {
-        if (screenX > 0 && screenY > 0 && mAmbientIndicationContainer != null
+        if (screenX < 0 || screenY < 0) return;
+        dispatchTouchEventToAmbientIndicationContainer(screenX, screenY);
+
+        mDozeInteractor.setLastTapToWakePosition(new Point((int) screenX, (int) screenY));
+    }
+
+    private void dispatchTouchEventToAmbientIndicationContainer(float screenX, float screenY) {
+        if (mAmbientIndicationContainer != null
                 && mAmbientIndicationContainer.getVisibility() == View.VISIBLE) {
             int[] locationOnScreen = new int[2];
             mAmbientIndicationContainer.getLocationOnScreen(locationOnScreen);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index 4ae2edc..4ccbc5a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -53,6 +53,7 @@
 import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragmentLogger;
 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusBarViewBinder;
 import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.window.StatusBarWindowStateController;
@@ -158,6 +159,7 @@
             StatusBarIconController statusBarIconController,
             StatusBarIconController.DarkIconManager.Factory darkIconManagerFactory,
             CollapsedStatusBarViewModel collapsedStatusBarViewModel,
+            CollapsedStatusBarViewBinder collapsedStatusBarViewBinder,
             StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager,
             KeyguardStateController keyguardStateController,
             ShadeViewController shadeViewController,
@@ -182,6 +184,7 @@
                 statusBarIconController,
                 darkIconManagerFactory,
                 collapsedStatusBarViewModel,
+                collapsedStatusBarViewBinder,
                 statusBarHideIconsForBouncerManager,
                 keyguardStateController,
                 shadeViewController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 0651a7b..fcae23b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -14,8 +14,6 @@
 
 package com.android.systemui.statusbar.phone.fragment;
 
-
-
 import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.IDLE;
 import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.SHOWING_PERSISTENT_DOT;
 
@@ -69,6 +67,7 @@
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallListener;
 import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusBarViewBinder;
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarVisibilityChangeListener;
 import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.window.StatusBarWindowStateController;
@@ -131,6 +130,7 @@
     private final StatusBarIconController mStatusBarIconController;
     private final CarrierConfigTracker mCarrierConfigTracker;
     private final CollapsedStatusBarViewModel mCollapsedStatusBarViewModel;
+    private final CollapsedStatusBarViewBinder mCollapsedStatusBarViewBinder;
     private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
     private final StatusBarIconController.DarkIconManager.Factory mDarkIconManagerFactory;
     private final SecureSettings mSecureSettings;
@@ -183,11 +183,21 @@
     private boolean mWaitingForWindowStateChangeAfterCameraLaunch = false;
 
     /**
+     * True when a transition from lockscreen to dream has started, but haven't yet received a
+     * status bar window state change afterward.
+     *
+     * Similar to [mWaitingForWindowStateChangeAfterCameraLaunch].
+     */
+    private boolean mTransitionFromLockscreenToDreamStarted = false;
+
+    /**
      * Listener that updates {@link #mWaitingForWindowStateChangeAfterCameraLaunch} when it receives
      * a new status bar window state.
      */
-    private final StatusBarWindowStateListener mStatusBarWindowStateListener = state ->
-            mWaitingForWindowStateChangeAfterCameraLaunch = false;
+    private final StatusBarWindowStateListener mStatusBarWindowStateListener = state -> {
+        mWaitingForWindowStateChangeAfterCameraLaunch = false;
+        mTransitionFromLockscreenToDreamStarted = false;
+    };
 
     @SuppressLint("ValidFragment")
     public CollapsedStatusBarFragment(
@@ -201,6 +211,7 @@
             StatusBarIconController statusBarIconController,
             StatusBarIconController.DarkIconManager.Factory darkIconManagerFactory,
             CollapsedStatusBarViewModel collapsedStatusBarViewModel,
+            CollapsedStatusBarViewBinder collapsedStatusBarViewBinder,
             StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager,
             KeyguardStateController keyguardStateController,
             ShadeViewController shadeViewController,
@@ -224,6 +235,7 @@
         mFeatureFlags = featureFlags;
         mStatusBarIconController = statusBarIconController;
         mCollapsedStatusBarViewModel = collapsedStatusBarViewModel;
+        mCollapsedStatusBarViewBinder = collapsedStatusBarViewBinder;
         mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager;
         mDarkIconManagerFactory = darkIconManagerFactory;
         mKeyguardStateController = keyguardStateController;
@@ -296,8 +308,8 @@
         mCarrierConfigTracker.addCallback(mCarrierConfigCallback);
         mCarrierConfigTracker.addDefaultDataSubscriptionChangedListener(mDefaultDataListener);
 
-        CollapsedStatusBarViewBinder.bind(
-                mStatusBar, mCollapsedStatusBarViewModel, this::updateStatusBarVisibilities);
+        mCollapsedStatusBarViewBinder.bind(
+                mStatusBar, mCollapsedStatusBarViewModel, mStatusBarVisibilityChangeListener);
     }
 
     @Override
@@ -411,6 +423,19 @@
         return mStatusBarFragmentComponent;
     }
 
+    private StatusBarVisibilityChangeListener mStatusBarVisibilityChangeListener =
+            new StatusBarVisibilityChangeListener() {
+        @Override
+        public void onStatusBarVisibilityMaybeChanged() {
+            updateStatusBarVisibilities(/* animate= */ true);
+        }
+
+        @Override
+        public void onTransitionFromLockscreenToDreamStarted() {
+            mTransitionFromLockscreenToDreamStarted = true;
+        }
+    };
+
     @Override
     public void disable(int displayId, int state1, int state2, boolean animate) {
         if (displayId != getContext().getDisplayId()) {
@@ -423,10 +448,6 @@
         updateStatusBarVisibilities(animate);
     }
 
-    private void updateStatusBarVisibilities() {
-        updateStatusBarVisibilities(/* animate= */ true);
-    }
-
     private void updateStatusBarVisibilities(boolean animate) {
         StatusBarVisibilityModel previousModel = mLastModifiedVisibility;
         StatusBarVisibilityModel newModel = calculateInternalModel(mLastSystemVisibility);
@@ -546,6 +567,18 @@
             return true;
         }
 
+        // Similar to [hideIconsForSecureCamera]: When dream is launched over lockscreen, the icons
+        // are momentarily visible because the dream animation has finished, but SysUI has not been
+        // informed that the dream is full-screen. For extra safety, we double-check that we're
+        // still dreaming.
+        final boolean hideIconsForDream =
+                mTransitionFromLockscreenToDreamStarted
+                        && mKeyguardUpdateMonitor.isDreaming()
+                        && mKeyguardStateController.isOccluded();
+        if (hideIconsForDream) {
+            return true;
+        }
+
         // While the status bar is transitioning from lockscreen to an occluded, we don't yet know
         // if the occluding activity is fullscreen or not. If it *is* fullscreen, we don't want to
         // briefly show the status bar just to immediately hide it again. So, we wait for the
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 7aa9033..27cc64f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -44,6 +44,8 @@
 import com.android.systemui.statusbar.pipeline.mobile.util.SubscriptionManagerProxyImpl
 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusBarViewBinder
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusBarViewBinderImpl
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositorySwitcher
@@ -107,6 +109,11 @@
         impl: CollapsedStatusBarViewModelImpl
     ): CollapsedStatusBarViewModel
 
+    @Binds
+    abstract fun collapsedStatusBarViewBinder(
+        impl: CollapsedStatusBarViewBinderImpl
+    ): CollapsedStatusBarViewBinder
+
     companion object {
         @Provides
         @SysUISingleton
@@ -160,7 +167,7 @@
         @SysUISingleton
         @SharedConnectivityInputLog
         fun provideSharedConnectivityTableLogBuffer(factory: LogBufferFactory): LogBuffer {
-            return factory.create("SharedConnectivityInputLog", 30)
+            return factory.create("SharedConnectivityInputLog", 60)
         }
 
         @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
index eb20bba..991ff56 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
@@ -21,6 +21,7 @@
 import com.android.settingslib.SignalIcon
 import com.android.settingslib.mobile.MobileMappings
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.demomode.DemoMode
 import com.android.systemui.demomode.DemoModeController
@@ -62,6 +63,7 @@
  */
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
 @OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
 class MobileRepositorySwitcher
 @Inject
 constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index 0e9b6c5..81a068d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -38,6 +38,7 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog
 import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
@@ -88,6 +89,7 @@
     private val context: Context,
     @Background private val bgDispatcher: CoroutineDispatcher,
     @Application private val scope: CoroutineScope,
+    airplaneModeRepository: AirplaneModeRepository,
     // Some "wifi networks" should be rendered as a mobile connection, which is why the wifi
     // repository is an input to the mobile repository.
     // See [CarrierMergedConnectionRepository] for details.
@@ -106,10 +108,20 @@
         context.getString(R.string.status_bar_network_name_separator)
 
     private val carrierMergedSubId: StateFlow<Int?> =
-        wifiRepository.wifiNetwork
-            .mapLatest {
-                if (it is WifiNetworkModel.CarrierMerged) {
-                    it.subscriptionId
+        combine(
+                wifiRepository.wifiNetwork,
+                connectivityRepository.defaultConnections,
+                airplaneModeRepository.isAirplaneMode,
+            ) { wifiNetwork, defaultConnections, isAirplaneMode ->
+                // The carrier merged connection should only be used if it's also the default
+                // connection or mobile connections aren't available because of airplane mode.
+                val defaultConnectionIsNonMobile =
+                    defaultConnections.carrierMerged.isDefault ||
+                        defaultConnections.wifi.isDefault ||
+                        isAirplaneMode
+
+                if (wifiNetwork is WifiNetworkModel.CarrierMerged && defaultConnectionIsNonMobile) {
+                    wifiNetwork.subscriptionId
                 } else {
                     null
                 }
@@ -269,12 +281,8 @@
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     override val hasCarrierMergedConnection: StateFlow<Boolean> =
-        combine(
-                connectivityRepository.defaultConnections,
-                carrierMergedSubId,
-            ) { defaultConnections, carrierMergedSubId ->
-                defaultConnections.carrierMerged.isDefault || carrierMergedSubId != null
-            }
+        carrierMergedSubId
+            .map { it != null }
             .distinctUntilChanged()
             .logDiffsForTable(
                 tableLogger,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index e1ffae0..e90f40c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -155,7 +155,8 @@
         combine(
                 unfilteredSubscriptions,
                 mobileConnectionsRepo.activeMobileDataSubscriptionId,
-            ) { unfilteredSubs, activeId ->
+                connectivityRepository.vcnSubId,
+            ) { unfilteredSubs, activeId, vcnSubId ->
                 // Based on the old logic,
                 if (unfilteredSubs.size != 2) {
                     return@combine unfilteredSubs
@@ -182,7 +183,13 @@
                     // return the non-opportunistic info
                     return@combine if (info1.isOpportunistic) listOf(info2) else listOf(info1)
                 } else {
-                    return@combine if (info1.subscriptionId == activeId) {
+                    // It's possible for the subId of the VCN to disagree with the active subId in
+                    // cases where the system has tried to switch but found no connection. In these
+                    // scenarios, VCN will always have the subId that we want to use, so use that
+                    // value instead of the activeId reported by telephony
+                    val subIdToKeep = vcnSubId ?: activeId
+
+                    return@combine if (info1.subscriptionId == subIdToKeep) {
                         listOf(info1)
                     } else {
                         listOf(info2)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt
index 051f43f..cac0ae3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt
@@ -61,6 +61,10 @@
             model::messagePrinter,
         )
     }
+
+    fun logVcnSubscriptionId(subId: Int) {
+        buffer.log(TAG, LogLevel.DEBUG, { int1 = subId }, { "vcnSubId changed: $int1" })
+    }
 }
 
 private const val TAG = "ConnectivityInputLogger"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt
index 731f1e0..7076f34 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt
@@ -27,6 +27,7 @@
 import android.net.NetworkCapabilities.TRANSPORT_WIFI
 import android.net.vcn.VcnTransportInfo
 import android.net.wifi.WifiInfo
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
 import androidx.annotation.ArrayRes
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.Dumpable
@@ -50,10 +51,13 @@
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharedFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.shareIn
 import kotlinx.coroutines.flow.stateIn
 
 /**
@@ -66,6 +70,16 @@
 
     /** Observable for which connection(s) are currently default. */
     val defaultConnections: StateFlow<DefaultConnectionModel>
+
+    /**
+     * Subscription ID of the [VcnTransportInfo] for the default connection.
+     *
+     * If the default network has a [VcnTransportInfo], then that transport info contains a subId of
+     * the VCN. When VCN is connected and default, this subId is what SystemUI will care about. In
+     * cases where telephony's activeDataSubscriptionId differs from this value, it is expected to
+     * eventually catch up and reflect what is represented here in the VcnTransportInfo.
+     */
+    val vcnSubId: StateFlow<Int?>
 }
 
 @SuppressLint("MissingPermission")
@@ -118,24 +132,13 @@
                 initialValue = defaultHiddenIcons
             )
 
-    @SuppressLint("MissingPermission")
-    override val defaultConnections: StateFlow<DefaultConnectionModel> =
+    private val defaultNetworkCapabilities: SharedFlow<NetworkCapabilities?> =
         conflatedCallbackFlow {
                 val callback =
                     object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
                         override fun onLost(network: Network) {
                             logger.logOnDefaultLost(network)
-                            // The system no longer has a default network, so everything is
-                            // non-default.
-                            trySend(
-                                DefaultConnectionModel(
-                                    Wifi(isDefault = false),
-                                    Mobile(isDefault = false),
-                                    CarrierMerged(isDefault = false),
-                                    Ethernet(isDefault = false),
-                                    isValidated = false,
-                                )
-                            )
+                            trySend(null)
                         }
 
                         override fun onCapabilitiesChanged(
@@ -143,30 +146,7 @@
                             networkCapabilities: NetworkCapabilities,
                         ) {
                             logger.logOnDefaultCapabilitiesChanged(network, networkCapabilities)
-
-                            val wifiInfo =
-                                networkCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
-
-                            val isWifiDefault =
-                                networkCapabilities.hasTransport(TRANSPORT_WIFI) || wifiInfo != null
-                            val isMobileDefault =
-                                networkCapabilities.hasTransport(TRANSPORT_CELLULAR)
-                            val isCarrierMergedDefault = wifiInfo?.isCarrierMerged == true
-                            val isEthernetDefault =
-                                networkCapabilities.hasTransport(TRANSPORT_ETHERNET)
-
-                            val isValidated =
-                                networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)
-
-                            trySend(
-                                DefaultConnectionModel(
-                                    Wifi(isWifiDefault),
-                                    Mobile(isMobileDefault),
-                                    CarrierMerged(isCarrierMergedDefault),
-                                    Ethernet(isEthernetDefault),
-                                    isValidated,
-                                )
-                            )
+                            trySend(networkCapabilities)
                         }
                     }
 
@@ -174,6 +154,61 @@
 
                 awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
             }
+            .shareIn(scope, SharingStarted.WhileSubscribed())
+
+    override val vcnSubId: StateFlow<Int?> =
+        defaultNetworkCapabilities
+            .map { networkCapabilities ->
+                networkCapabilities?.run {
+                    val subId = (transportInfo as? VcnTransportInfo)?.subId
+                    // Never return an INVALID_SUBSCRIPTION_ID (-1)
+                    if (subId != INVALID_SUBSCRIPTION_ID) {
+                        subId
+                    } else {
+                        null
+                    }
+                }
+            }
+            .distinctUntilChanged()
+            /* A note for logging: we use -2 here since -1 == INVALID_SUBSCRIPTION_ID */
+            .onEach { logger.logVcnSubscriptionId(it ?: -2) }
+            .stateIn(scope, SharingStarted.Eagerly, null)
+
+    @SuppressLint("MissingPermission")
+    override val defaultConnections: StateFlow<DefaultConnectionModel> =
+        defaultNetworkCapabilities
+            .map { networkCapabilities ->
+                if (networkCapabilities == null) {
+                    // The system no longer has a default network, so everything is
+                    // non-default.
+                    DefaultConnectionModel(
+                        Wifi(isDefault = false),
+                        Mobile(isDefault = false),
+                        CarrierMerged(isDefault = false),
+                        Ethernet(isDefault = false),
+                        isValidated = false,
+                    )
+                } else {
+                    val wifiInfo =
+                        networkCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
+
+                    val isWifiDefault =
+                        networkCapabilities.hasTransport(TRANSPORT_WIFI) || wifiInfo != null
+                    val isMobileDefault = networkCapabilities.hasTransport(TRANSPORT_CELLULAR)
+                    val isCarrierMergedDefault = wifiInfo?.isCarrierMerged == true
+                    val isEthernetDefault = networkCapabilities.hasTransport(TRANSPORT_ETHERNET)
+
+                    val isValidated = networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)
+
+                    DefaultConnectionModel(
+                        Wifi(isWifiDefault),
+                        Mobile(isMobileDefault),
+                        CarrierMerged(isCarrierMergedDefault),
+                        Ethernet(isEthernetDefault),
+                        isValidated,
+                    )
+                }
+            }
             .distinctUntilChanged()
             .onEach { logger.logDefaultConnectionsChanged(it) }
             .stateIn(scope, SharingStarted.Eagerly, DefaultConnectionModel())
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
index 9a59851..b9b88f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
@@ -19,34 +19,61 @@
 import android.view.View
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.launch
 
-object CollapsedStatusBarViewBinder {
+/**
+ * Interface to assist with binding the [CollapsedStatusBarFragment] to
+ * [CollapsedStatusBarViewModel]. Used only to enable easy testing of [CollapsedStatusBarFragment].
+ */
+interface CollapsedStatusBarViewBinder {
     /**
      * Binds the view to the view-model. [listener] will be notified whenever an event that may
      * change the status bar visibility occurs.
      */
-    @JvmStatic
     fun bind(
         view: View,
         viewModel: CollapsedStatusBarViewModel,
         listener: StatusBarVisibilityChangeListener,
+    )
+}
+
+@SysUISingleton
+class CollapsedStatusBarViewBinderImpl @Inject constructor() : CollapsedStatusBarViewBinder {
+    override fun bind(
+        view: View,
+        viewModel: CollapsedStatusBarViewModel,
+        listener: StatusBarVisibilityChangeListener,
     ) {
         view.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.CREATED) {
-                viewModel.isTransitioningFromLockscreenToOccluded.collect {
-                    listener.onStatusBarVisibilityMaybeChanged()
+                launch {
+                    viewModel.isTransitioningFromLockscreenToOccluded.collect {
+                        listener.onStatusBarVisibilityMaybeChanged()
+                    }
+                }
+
+                launch {
+                    viewModel.transitionFromLockscreenToDreamStartedEvent.collect {
+                        listener.onTransitionFromLockscreenToDreamStarted()
+                    }
                 }
             }
         }
     }
 }
 
-/**
- * Listener to be notified when the status bar visibility might have changed due to the device
- * moving to a different state.
- */
-fun interface StatusBarVisibilityChangeListener {
+/** Listener for various events that may affect the status bar's visibility. */
+interface StatusBarVisibilityChangeListener {
+    /**
+     * Called when the status bar visibility might have changed due to the device moving to a
+     * different state.
+     */
     fun onStatusBarVisibilityMaybeChanged()
+
+    /** Called when a transition from lockscreen to dream has started. */
+    fun onTransitionFromLockscreenToDreamStarted()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt
index edb7e4d..15ab143 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt
@@ -22,8 +22,10 @@
 import com.android.systemui.keyguard.shared.model.TransitionState
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
@@ -43,6 +45,9 @@
      * otherwise.
      */
     val isTransitioningFromLockscreenToOccluded: StateFlow<Boolean>
+
+    /** Emits whenever a transition from lockscreen to dream has started. */
+    val transitionFromLockscreenToDreamStartedEvent: Flow<Unit>
 }
 
 @SysUISingleton
@@ -59,4 +64,9 @@
                     it.transitionState == TransitionState.RUNNING
             }
             .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), initialValue = false)
+
+    override val transitionFromLockscreenToDreamStartedEvent: Flow<Unit> =
+        keyguardTransitionInteractor.lockscreenToDreamingTransition
+            .filter { it.transitionState == TransitionState.STARTED }
+            .map {}
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
index b129617..e96288a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
@@ -19,6 +19,7 @@
 import android.os.Bundle
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.demomode.DemoMode
 import com.android.systemui.demomode.DemoModeController
@@ -54,6 +55,7 @@
  */
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
 @OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
 class WifiRepositorySwitcher
 @Inject
 constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 5208064..5cc3d52 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -38,7 +38,11 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepository;
+import com.android.systemui.statusbar.policy.bluetooth.ConnectionStatusModel;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -50,14 +54,20 @@
 import javax.inject.Inject;
 
 /**
+ * Controller for information about bluetooth connections.
+ *
+ * Note: Right now, this class and {@link BluetoothRepository} co-exist. Any new code should go in
+ * {@link BluetoothRepository}, but external clients should query this file for now.
  */
 @SysUISingleton
 public class BluetoothControllerImpl implements BluetoothController, BluetoothCallback,
         CachedBluetoothDevice.Callback, LocalBluetoothProfileManager.ServiceListener {
     private static final String TAG = "BluetoothController";
 
+    private final FeatureFlags mFeatureFlags;
     private final DumpManager mDumpManager;
     private final BluetoothLogger mLogger;
+    private final BluetoothRepository mBluetoothRepository;
     private final LocalBluetoothManager mLocalBluetoothManager;
     private final UserManager mUserManager;
     private final int mCurrentUser;
@@ -79,14 +89,18 @@
     @Inject
     public BluetoothControllerImpl(
             Context context,
+            FeatureFlags featureFlags,
             UserTracker userTracker,
             DumpManager dumpManager,
             BluetoothLogger logger,
+            BluetoothRepository bluetoothRepository,
             @Main Looper mainLooper,
             @Nullable LocalBluetoothManager localBluetoothManager,
             @Nullable BluetoothAdapter bluetoothAdapter) {
+        mFeatureFlags = featureFlags;
         mDumpManager = dumpManager;
         mLogger = logger;
+        mBluetoothRepository = bluetoothRepository;
         mLocalBluetoothManager = localBluetoothManager;
         mHandler = new H(mainLooper);
         if (mLocalBluetoothManager != null) {
@@ -229,6 +243,16 @@
     }
 
     private void updateConnected() {
+        if (mFeatureFlags.isEnabled(Flags.NEW_BLUETOOTH_REPOSITORY)) {
+            mBluetoothRepository.fetchConnectionStatusInBackground(
+                    getDevices(), this::onConnectionStatusFetched);
+        } else {
+            updateConnectedOld();
+        }
+    }
+
+    /** Used only if {@link Flags.NEW_BLUETOOTH_REPOSITORY} is *not* enabled. */
+    private void updateConnectedOld() {
         // Make sure our connection state is up to date.
         int state = mLocalBluetoothManager.getBluetoothAdapter().getConnectionState();
         List<CachedBluetoothDevice> newList = new ArrayList<>();
@@ -249,6 +273,12 @@
             // connected.
             state = BluetoothAdapter.STATE_DISCONNECTED;
         }
+        onConnectionStatusFetched(new ConnectionStatusModel(state, newList));
+    }
+
+    private void onConnectionStatusFetched(ConnectionStatusModel status) {
+        List<CachedBluetoothDevice> newList = status.getConnectedDevices();
+        int state = status.getMaxConnectionState();
         synchronized (mConnectedDevices) {
             mConnectedDevices.clear();
             mConnectedDevices.addAll(newList);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepository.kt
new file mode 100644
index 0000000..80f3d76
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepository.kt
@@ -0,0 +1,109 @@
+/*
+ * 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.policy.bluetooth
+
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothProfile
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+/**
+ * Repository for information about bluetooth connections.
+ *
+ * Note: Right now, this class and [BluetoothController] co-exist. Any new code should go in this
+ * implementation, but external clients should query [BluetoothController] instead of this class for
+ * now.
+ */
+interface BluetoothRepository {
+    /**
+     * Fetches the connection statuses for the given [currentDevices] and invokes [callback] once
+     * those statuses have been fetched. The fetching occurs on a background thread because IPCs may
+     * be required to fetch the statuses (see b/271058380).
+     */
+    fun fetchConnectionStatusInBackground(
+        currentDevices: Collection<CachedBluetoothDevice>,
+        callback: ConnectionStatusFetchedCallback,
+    )
+}
+
+/** Implementation of [BluetoothRepository]. */
+@SysUISingleton
+class BluetoothRepositoryImpl
+@Inject
+constructor(
+    @Application private val scope: CoroutineScope,
+    @Background private val bgDispatcher: CoroutineDispatcher,
+    private val localBluetoothManager: LocalBluetoothManager?,
+) : BluetoothRepository {
+    override fun fetchConnectionStatusInBackground(
+        currentDevices: Collection<CachedBluetoothDevice>,
+        callback: ConnectionStatusFetchedCallback,
+    ) {
+        scope.launch {
+            val result = fetchConnectionStatus(currentDevices)
+            callback.onConnectionStatusFetched(result)
+        }
+    }
+
+    private suspend fun fetchConnectionStatus(
+        currentDevices: Collection<CachedBluetoothDevice>,
+    ): ConnectionStatusModel {
+        return withContext(bgDispatcher) {
+            val minimumMaxConnectionState =
+                localBluetoothManager?.bluetoothAdapter?.connectionState
+                    ?: BluetoothProfile.STATE_DISCONNECTED
+            var maxConnectionState =
+                if (currentDevices.isEmpty()) {
+                    minimumMaxConnectionState
+                } else {
+                    currentDevices
+                        .maxOf { it.maxConnectionState }
+                        .coerceAtLeast(minimumMaxConnectionState)
+                }
+
+            val connectedDevices = currentDevices.filter { it.isConnected }
+
+            if (
+                connectedDevices.isEmpty() && maxConnectionState == BluetoothAdapter.STATE_CONNECTED
+            ) {
+                // If somehow we think we are connected, but have no connected devices, we aren't
+                // connected.
+                maxConnectionState = BluetoothAdapter.STATE_DISCONNECTED
+            }
+
+            ConnectionStatusModel(maxConnectionState, connectedDevices)
+        }
+    }
+}
+
+data class ConnectionStatusModel(
+    /** The maximum connection state out of all current devices. */
+    val maxConnectionState: Int,
+    /** A list of devices that are currently connected. */
+    val connectedDevices: List<CachedBluetoothDevice>,
+)
+
+/** Callback notified when the new status has been fetched. */
+fun interface ConnectionStatusFetchedCallback {
+    fun onConnectionStatusFetched(status: ConnectionStatusModel)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
index 1b73539..e1a7b6d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
@@ -62,15 +62,16 @@
 import com.android.systemui.statusbar.policy.WalletControllerImpl;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.statusbar.policy.ZenModeControllerImpl;
-
-import java.util.concurrent.Executor;
-
-import javax.inject.Named;
+import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepository;
+import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepositoryImpl;
 
 import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
 
+import java.util.concurrent.Executor;
+
+import javax.inject.Named;
 
 /** Dagger Module for code in the statusbar.policy package. */
 @Module
@@ -84,6 +85,10 @@
 
     /** */
     @Binds
+    BluetoothRepository provideBluetoothRepository(BluetoothRepositoryImpl impl);
+
+    /** */
+    @Binds
     CastController provideCastController(CastControllerImpl controllerImpl);
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt b/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt
index 58f2246..57b9f91 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt
+++ b/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt
@@ -56,18 +56,6 @@
                 Pair.create("on_error", MDC.onError()),
                 Pair.create("error_container", MDC.errorContainer()),
                 Pair.create("on_error_container", MDC.onErrorContainer()),
-                Pair.create("primary_fixed", MDC.primaryFixed()),
-                Pair.create("primary_fixed_dim", MDC.primaryFixedDim()),
-                Pair.create("on_primary_fixed", MDC.onPrimaryFixed()),
-                Pair.create("on_primary_fixed_variant", MDC.onPrimaryFixedVariant()),
-                Pair.create("secondary_fixed", MDC.secondaryFixed()),
-                Pair.create("secondary_fixed_dim", MDC.secondaryFixedDim()),
-                Pair.create("on_secondary_fixed", MDC.onSecondaryFixed()),
-                Pair.create("on_secondary_fixed_variant", MDC.onSecondaryFixedVariant()),
-                Pair.create("tertiary_fixed", MDC.tertiaryFixed()),
-                Pair.create("tertiary_fixed_dim", MDC.tertiaryFixedDim()),
-                Pair.create("on_tertiary_fixed", MDC.onTertiaryFixed()),
-                Pair.create("on_tertiary_fixed_variant", MDC.onTertiaryFixedVariant()),
                 Pair.create("control_activated", MDC.controlActivated()),
                 Pair.create("control_normal", MDC.controlNormal()),
                 Pair.create("control_highlight", MDC.controlHighlight()),
@@ -92,7 +80,24 @@
                 Pair.create(
                     "palette_key_color_neutral_variant",
                     MDC.neutralVariantPaletteKeyColor()
-                )
+                ),
+            )
+
+        @JvmField
+        val FIXED_COLORS_MAPPED: List<Pair<String, DynamicColor>> =
+            arrayListOf(
+                Pair.create("primary_fixed", MDC.primaryFixed()),
+                Pair.create("primary_fixed_dim", MDC.primaryFixedDim()),
+                Pair.create("on_primary_fixed", MDC.onPrimaryFixed()),
+                Pair.create("on_primary_fixed_variant", MDC.onPrimaryFixedVariant()),
+                Pair.create("secondary_fixed", MDC.secondaryFixed()),
+                Pair.create("secondary_fixed_dim", MDC.secondaryFixedDim()),
+                Pair.create("on_secondary_fixed", MDC.onSecondaryFixed()),
+                Pair.create("on_secondary_fixed_variant", MDC.onSecondaryFixedVariant()),
+                Pair.create("tertiary_fixed", MDC.tertiaryFixed()),
+                Pair.create("tertiary_fixed_dim", MDC.tertiaryFixedDim()),
+                Pair.create("on_tertiary_fixed", MDC.onTertiaryFixed()),
+                Pair.create("on_tertiary_fixed_variant", MDC.onTertiaryFixedVariant()),
             )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 4b73d61..c1999b2 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -624,6 +624,7 @@
         FabricatedOverlay overlay = newFabricatedOverlay("dynamic");
         assignDynamicPaletteToOverlay(overlay, true /* isDark */);
         assignDynamicPaletteToOverlay(overlay, false /* isDark */);
+        assignFixedColorsToOverlay(overlay);
         return overlay;
     }
 
@@ -638,6 +639,15 @@
         });
     }
 
+    private void assignFixedColorsToOverlay(FabricatedOverlay overlay) {
+        DynamicColors.FIXED_COLORS_MAPPED.forEach(p -> {
+            String resourceName = "android:color/system_" + p.first;
+            int colorValue = p.second.getArgb(mDynamicSchemeLight);
+            overlay.setResourceValue(resourceName, TYPE_INT_COLOR_ARGB8, colorValue,
+                    null /* configuration */);
+        });
+    }
+
     /**
      * Checks if the color scheme in mColorScheme matches the current system palettes.
      * @param managedProfiles List of managed profiles for this user.
@@ -666,7 +676,9 @@
                     && res.getColor(android.R.color.system_primary_container_dark, theme)
                     == MaterialDynamicColors.primaryContainer().getArgb(mDynamicSchemeDark)
                     && res.getColor(android.R.color.system_primary_container_light, theme)
-                    == MaterialDynamicColors.primaryContainer().getArgb(mDynamicSchemeLight))) {
+                    == MaterialDynamicColors.primaryContainer().getArgb(mDynamicSchemeLight)
+                    && res.getColor(android.R.color.system_primary_fixed, theme)
+                    == MaterialDynamicColors.primaryFixed().getArgb(mDynamicSchemeLight))) {
                 return false;
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
index 0a78d896..d9a8e0c 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
@@ -23,8 +23,6 @@
 import android.hardware.SensorPrivacyManager;
 import android.os.Handler;
 
-import androidx.annotation.Nullable;
-
 import com.android.internal.logging.UiEventLogger;
 import com.android.keyguard.KeyguardViewController;
 import com.android.systemui.dagger.ReferenceSystemUIModule;
@@ -75,13 +73,13 @@
 import com.android.systemui.statusbar.tv.notifications.TvNotificationHandler;
 import com.android.systemui.volume.dagger.VolumeModule;
 
-import javax.inject.Named;
-
 import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
 import dagger.multibindings.IntoSet;
 
+import javax.inject.Named;
+
 /**
  * A TV specific version of {@link ReferenceSystemUIModule}.
  *
@@ -105,9 +103,8 @@
     @SysUISingleton
     @Provides
     @Named(LEAK_REPORT_EMAIL_NAME)
-    @Nullable
     static String provideLeakReportEmail() {
-        return null;
+        return "";
     }
 
     @Binds
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt
new file mode 100644
index 0000000..73e2f97
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Utils.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.util.kotlin
+
+class Utils {
+    companion object {
+        fun <A, B, C> toTriple(a: A, bc: Pair<B, C>) = Triple(a, bc.first, bc.second)
+
+        fun <A, B, C, D> toQuad(a: A, b: B, c: C, d: D) = Quad(a, b, c, d)
+        fun <A, B, C, D> toQuad(a: A, bcd: Triple<B, C, D>) =
+            Quad(a, bcd.first, bcd.second, bcd.third)
+
+        fun <A, B, C, D, E> toQuint(a: A, bcde: Quad<B, C, D, E>) =
+            Quint(a, bcde.first, bcde.second, bcde.third, bcde.fourth)
+    }
+}
+
+data class Quad<A, B, C, D>(val first: A, val second: B, val third: C, val fourth: D)
+
+data class Quint<A, B, C, D, E>(
+    val first: A,
+    val second: B,
+    val third: C,
+    val fourth: D,
+    val fifth: E
+)
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/LeakReporter.java b/packages/SystemUI/src/com/android/systemui/util/leak/LeakReporter.java
index a0d22f3..c1b7d7a 100644
--- a/packages/SystemUI/src/com/android/systemui/util/leak/LeakReporter.java
+++ b/packages/SystemUI/src/com/android/systemui/util/leak/LeakReporter.java
@@ -18,7 +18,6 @@
 
 import static com.android.systemui.Dependency.LEAK_REPORT_EMAIL_NAME;
 
-import android.annotation.Nullable;
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
@@ -29,6 +28,7 @@
 import android.net.Uri;
 import android.os.Debug;
 import android.os.SystemProperties;
+import android.text.TextUtils;
 import android.util.Log;
 
 import androidx.core.content.FileProvider;
@@ -68,7 +68,7 @@
 
     @Inject
     public LeakReporter(Context context, UserTracker userTracker, LeakDetector leakDetector,
-            @Nullable @Named(LEAK_REPORT_EMAIL_NAME) String leakReportEmail) {
+            @Named(LEAK_REPORT_EMAIL_NAME) String leakReportEmail) {
         mContext = context;
         mUserTracker = userTracker;
         mLeakDetector = leakDetector;
@@ -150,9 +150,8 @@
         intent.setClipData(clipData);
         intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments);
 
-        String leakReportEmail = mLeakReportEmail;
-        if (leakReportEmail != null) {
-            intent.putExtra(Intent.EXTRA_EMAIL, new String[] { leakReportEmail });
+        if (!TextUtils.isEmpty(mLeakReportEmail)) {
+            intent.putExtra(Intent.EXTRA_EMAIL, new String[] { mLeakReportEmail });
         }
 
         return intent;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/Events.java b/packages/SystemUI/src/com/android/systemui/volume/Events.java
index fc0033d..2d1e622 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/Events.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/Events.java
@@ -98,6 +98,8 @@
     public static final int DISMISS_REASON_OUTPUT_CHOOSER = 8;
     public static final int DISMISS_REASON_USB_OVERHEAD_ALARM_CHANGED = 9;
     public static final int DISMISS_REASON_CSD_WARNING_TIMEOUT = 10;
+    public static final int DISMISS_REASON_POSTURE_CHANGED = 11;
+
     public static final String[] DISMISS_REASONS = {
             "unknown",
             "touch_outside",
@@ -109,7 +111,8 @@
             "a11y_stream_changed",
             "output_chooser",
             "usb_temperature_below_threshold",
-            "csd_warning_timeout"
+            "csd_warning_timeout",
+            "posture_changed"
     };
 
     public static final int SHOW_REASON_UNKNOWN = 0;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 91078dc..f893cf4 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -34,6 +34,7 @@
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_VOLUME_CONTROL;
 import static com.android.internal.jank.InteractionJankMonitor.Configuration.Builder;
+import static com.android.systemui.volume.Events.DISMISS_REASON_POSTURE_CHANGED;
 import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED;
 
 import android.animation.Animator;
@@ -129,6 +130,7 @@
 import com.android.systemui.plugins.VolumeDialogController.StreamState;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.util.AlphaTintDrawableWrapper;
 import com.android.systemui.util.DeviceConfigProxy;
@@ -184,7 +186,7 @@
     private final boolean mChangeVolumeRowTintWhenInactive;
 
     private final Context mContext;
-    private final H mHandler = new H();
+    private final H mHandler;
     private final VolumeDialogController mController;
     private final DeviceProvisionedController mDeviceProvisionedController;
     private final Region mTouchableRegion = new Region();
@@ -259,16 +261,13 @@
     private final AccessibilityManagerWrapper mAccessibilityMgr;
     private final Object mSafetyWarningLock = new Object();
     private final Accessibility mAccessibility = new Accessibility();
-
     private final ConfigurationController mConfigurationController;
     private final MediaOutputDialogFactory mMediaOutputDialogFactory;
     private final VolumePanelFactory mVolumePanelFactory;
     private final CsdWarningDialog.Factory mCsdWarningDialogFactory;
     private final ActivityStarter mActivityStarter;
-
     private boolean mShowing;
     private boolean mShowA11yStream;
-
     private int mActiveStream;
     private int mPrevActiveStream;
     private boolean mAutomute = VolumePrefs.DEFAULT_ENABLE_AUTOMUTE;
@@ -300,6 +299,12 @@
     @VisibleForTesting
     int mVolumeRingerMuteIconDrawableId;
 
+    private int mOriginalGravity;
+    private final DevicePostureController.Callback mDevicePostureControllerCallback;
+    private final DevicePostureController mDevicePostureController;
+    private @DevicePostureController.DevicePostureInt int mDevicePosture;
+    private int mOrientation;
+
     public VolumeDialogImpl(
             Context context,
             VolumeDialogController volumeDialogController,
@@ -313,9 +318,12 @@
             DeviceConfigProxy deviceConfigProxy,
             Executor executor,
             CsdWarningDialog.Factory csdWarningDialogFactory,
+            DevicePostureController devicePostureController,
+            Looper looper,
             DumpManager dumpManager) {
         mContext =
                 new ContextThemeWrapper(context, R.style.volume_dialog_theme);
+        mHandler = new H(looper);
         mController = volumeDialogController;
         mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
         mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
@@ -357,6 +365,16 @@
 
         initDimens();
 
+        mOrientation = mContext.getResources().getConfiguration().orientation;
+        mDevicePostureController = devicePostureController;
+        if (mDevicePostureController != null) {
+            int initialPosture = mDevicePostureController.getDevicePosture();
+            mDevicePosture = initialPosture;
+            mDevicePostureControllerCallback = this::onPostureChanged;
+        } else {
+            mDevicePostureControllerCallback = null;
+        }
+
         mDeviceConfigProxy = deviceConfigProxy;
         mExecutor = executor;
         mSeparateNotification = mDeviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
@@ -365,6 +383,25 @@
     }
 
     /**
+     * Adjust the dialog location on the screen in order to avoid drawing on the hinge.
+     */
+    private void adjustPositionOnScreen() {
+        final boolean isPortrait = mOrientation == Configuration.ORIENTATION_PORTRAIT;
+        final boolean isHalfOpen =
+                mDevicePosture == DevicePostureController.DEVICE_POSTURE_HALF_OPENED;
+        final boolean isTabletop = isPortrait && isHalfOpen;
+        WindowManager.LayoutParams lp =  mWindow.getAttributes();
+        int gravity = isTabletop ? (mOriginalGravity | Gravity.TOP) : mOriginalGravity;
+        mWindowGravity = Gravity.getAbsoluteGravity(gravity,
+                mContext.getResources().getConfiguration().getLayoutDirection());
+        lp.gravity = mWindowGravity;
+    }
+
+    @VisibleForTesting int getWindowGravity() {
+        return mWindowGravity;
+    }
+
+    /**
      * If ringer and notification are the same stream (T and earlier), use notification-like bell
      * icon set.
      * If ringer and notification are separated, then use generic speaker icons.
@@ -419,6 +456,10 @@
 
         mDeviceConfigProxy.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
                 mExecutor, this::onDeviceConfigChange);
+
+        if (mDevicePostureController != null) {
+            mDevicePostureController.addCallback(mDevicePostureControllerCallback);
+        }
     }
 
     @Override
@@ -427,6 +468,9 @@
         mHandler.removeCallbacksAndMessages(null);
         mConfigurationController.removeCallback(this);
         mDeviceConfigProxy.removeOnPropertiesChangedListener(this::onDeviceConfigChange);
+        if (mDevicePostureController != null) {
+            mDevicePostureController.removeCallback(mDevicePostureControllerCallback);
+        }
     }
 
     /**
@@ -441,7 +485,6 @@
                 mSeparateNotification = newVal;
                 updateRingerModeIconSet();
                 updateRingRowIcon();
-
             }
         }
     }
@@ -500,7 +543,6 @@
 
     private void initDialog(int lockTaskModeState) {
         mDialog = new CustomDialog(mContext);
-
         initDimens();
 
         mConfigurableTexts = new ConfigurableTexts(mContext);
@@ -524,14 +566,13 @@
         lp.setTitle(VolumeDialogImpl.class.getSimpleName());
         lp.windowAnimations = -1;
 
-        mWindowGravity = Gravity.getAbsoluteGravity(
-                mContext.getResources().getInteger(R.integer.volume_dialog_gravity),
+        mOriginalGravity = mContext.getResources().getInteger(R.integer.volume_dialog_gravity);
+        mWindowGravity = Gravity.getAbsoluteGravity(mOriginalGravity,
                 mContext.getResources().getConfiguration().getLayoutDirection());
         lp.gravity = mWindowGravity;
 
         mWindow.setAttributes(lp);
         mWindow.setLayout(WRAP_CONTENT, WRAP_CONTENT);
-
         mDialog.setContentView(R.layout.volume_dialog);
         mDialogView = mDialog.findViewById(R.id.volume_dialog);
         mDialogView.setAlpha(0);
@@ -1539,8 +1580,10 @@
             animator.translationX(
                     (isWindowGravityLeft() ? -1 : 1) * mDialogView.getWidth() / 2.0f);
         }
+
         animator.setListener(getJankListener(getDialogView(), TYPE_DISMISS,
                 mDialogHideAnimationDurationMs)).start();
+
         checkODICaptionsTooltip(true);
         synchronized (mSafetyWarningLock) {
             if (mSafetyWarning != null) {
@@ -2237,6 +2280,11 @@
         mTopContainer.setBackground(background);
     }
 
+    @Override
+    public void onConfigChanged(Configuration config) {
+        mOrientation = config.orientation;
+    }
+
     private final VolumeDialogController.Callbacks mControllerCallbackH
             = new VolumeDialogController.Callbacks() {
         @Override
@@ -2313,6 +2361,11 @@
         }
     };
 
+    @VisibleForTesting void onPostureChanged(int posture) {
+        dismiss(DISMISS_REASON_POSTURE_CHANGED);
+        mDevicePosture = posture;
+    }
+
     private final class H extends Handler {
         private static final int SHOW = 1;
         private static final int DISMISS = 2;
@@ -2323,8 +2376,8 @@
         private static final int STATE_CHANGED = 7;
         private static final int CSD_TIMEOUT = 8;
 
-        public H() {
-            super(Looper.getMainLooper());
+        H(Looper looper) {
+            super(looper);
         }
 
         @Override
@@ -2370,6 +2423,7 @@
         protected void onStart() {
             super.setCanceledOnTouchOutside(true);
             super.onStart();
+            adjustPositionOnScreen();
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index 14d3ca3..bb04f82 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.media.AudioManager;
+import android.os.Looper;
 
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -28,6 +29,7 @@
 import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.volume.CsdWarningDialog;
@@ -42,7 +44,6 @@
 
 import java.util.concurrent.Executor;
 
-
 /** Dagger Module for code in the volume package. */
 @Module
 public interface VolumeModule {
@@ -65,6 +66,7 @@
             DeviceConfigProxy deviceConfigProxy,
             @Main Executor executor,
             CsdWarningDialog.Factory csdFactory,
+            DevicePostureController devicePostureController,
             DumpManager dumpManager) {
         VolumeDialogImpl impl = new VolumeDialogImpl(
                 context,
@@ -79,6 +81,8 @@
                 deviceConfigProxy,
                 executor,
                 csdFactory,
+                devicePostureController,
+                Looper.getMainLooper(),
                 dumpManager);
         impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
         impl.setAutomute(true);
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java
index 492f231..81d04d4 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java
@@ -338,7 +338,12 @@
          */
         QAWalletCardViewInfo(Context context, WalletCard walletCard) {
             mWalletCard = walletCard;
-            mCardDrawable = mWalletCard.getCardImage().loadDrawable(context);
+            Icon cardImageIcon = mWalletCard.getCardImage();
+            if (cardImageIcon.getType() == Icon.TYPE_URI) {
+                mCardDrawable = null;
+            } else {
+                mCardDrawable = mWalletCard.getCardImage().loadDrawable(context);
+            }
             Icon icon = mWalletCard.getCardIcon();
             mIconDrawable = icon == null ? null : icon.loadDrawable(context);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index cd1ad1b..316b54e 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -173,7 +173,7 @@
                     .isLockscreenLiveWallpaperEnabled();
             mSurfaceHolder = surfaceHolder;
             Rect dimensions = mIsLockscreenLiveWallpaperEnabled
-                    ? mWallpaperManager.peekBitmapDimensions(getSourceFlag())
+                    ? mWallpaperManager.peekBitmapDimensions(getSourceFlag(), true)
                     : mWallpaperManager.peekBitmapDimensions();
             int width = Math.max(MIN_SURFACE_WIDTH, dimensions.width());
             int height = Math.max(MIN_SURFACE_HEIGHT, dimensions.height());
@@ -325,7 +325,7 @@
             try {
                 bitmap = mIsLockscreenLiveWallpaperEnabled
                         ? mWallpaperManager.getBitmapAsUser(
-                                mUserTracker.getUserId(), false, getSourceFlag())
+                                mUserTracker.getUserId(), false, getSourceFlag(), true)
                         : mWallpaperManager.getBitmapAsUser(mUserTracker.getUserId(), false);
                 if (bitmap != null
                         && bitmap.getByteCount() > RecordingCanvas.MAX_BITMAP_SIZE) {
@@ -347,7 +347,7 @@
                 try {
                     bitmap = mIsLockscreenLiveWallpaperEnabled
                             ? mWallpaperManager.getBitmapAsUser(
-                                    mUserTracker.getUserId(), false, getSourceFlag())
+                                    mUserTracker.getUserId(), false, getSourceFlag(), true)
                             : mWallpaperManager.getBitmapAsUser(mUserTracker.getUserId(), false);
                 } catch (RuntimeException | OutOfMemoryError e) {
                     Log.w(TAG, "Unable to load default wallpaper!", e);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
index 5557efa..0b9ba782 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
@@ -47,6 +47,7 @@
 import android.testing.AndroidTestingRunner;
 import android.text.TextUtils;
 
+import com.android.keyguard.logging.CarrierTextManagerLogger;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
@@ -96,6 +97,8 @@
     @Mock
     private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @Mock
+    private CarrierTextManagerLogger mLogger;
+    @Mock
     private PackageManager mPackageManager;
     @Mock
     private TelephonyManager mTelephonyManager;
@@ -144,7 +147,7 @@
         mCarrierTextManager = new CarrierTextManager.Builder(
                 mContext, mContext.getResources(), mWifiRepository,
                 mTelephonyManager, mTelephonyListenerManager, mWakefulnessLifecycle, mMainExecutor,
-                mBgExecutor, mKeyguardUpdateMonitor)
+                mBgExecutor, mKeyguardUpdateMonitor, mLogger)
                 .setShowAirplaneMode(true)
                 .setShowMissingSim(true)
                 .build();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 19d5278..3f1560b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -136,7 +136,7 @@
         runBlocking(IMMEDIATE) {
             underTest.registerListeners(parentView)
 
-            repository.setDozing(true)
+            repository.setIsDozing(true)
             repository.setDozeAmount(1f)
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
index 9e600f5..7531cb4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
@@ -75,7 +75,6 @@
         MockitoAnnotations.initMocks(this)
         keyguardBouncerRepository =
             KeyguardBouncerRepositoryImpl(
-                mock(com.android.keyguard.ViewMediatorCallback::class.java),
                 FakeSystemClock(),
                 testScope.backgroundScope,
                 bouncerLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
index f3a100b..239e317 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
@@ -102,6 +102,12 @@
                                 540 /* sensorLocationX */,
                                 1636 /* sensorLocationY */,
                                 130 /* sensorRadius */
+                            ),
+                            SensorLocationInternal(
+                                "display_id_1" /* displayId */,
+                                100 /* sensorLocationX */,
+                                300 /* sensorLocationY */,
+                                20 /* sensorRadius */
                             )
                         )
                     )
@@ -112,7 +118,17 @@
             assertThat(repository.sensorId.value).isEqualTo(1)
             assertThat(repository.strength.value).isEqualTo(SensorStrength.STRONG)
             assertThat(repository.sensorType.value).isEqualTo(FingerprintSensorType.REAR)
-            with(repository.sensorLocation.value) {
+
+            assertThat(repository.sensorLocations.value.size).isEqualTo(2)
+            assertThat(repository.sensorLocations.value).containsKey("display_id_1")
+            with(repository.sensorLocations.value["display_id_1"]!!) {
+                assertThat(displayId).isEqualTo("display_id_1")
+                assertThat(sensorLocationX).isEqualTo(100)
+                assertThat(sensorLocationY).isEqualTo(300)
+                assertThat(sensorRadius).isEqualTo(20)
+            }
+            assertThat(repository.sensorLocations.value).containsKey("")
+            with(repository.sensorLocations.value[""]!!) {
                 assertThat(displayId).isEqualTo("")
                 assertThat(sensorLocationX).isEqualTo(540)
                 assertThat(sensorLocationY).isEqualTo(1636)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt
new file mode 100644
index 0000000..fd96cf4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.domain.interactor
+
+import android.hardware.biometrics.SensorLocationInternal
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RunWith(JUnit4::class)
+class SideFpsOverlayInteractorTest : SysuiTestCase() {
+
+    @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+    private lateinit var testScope: TestScope
+
+    private val fingerprintRepository = FakeFingerprintPropertyRepository()
+
+    private lateinit var interactor: SideFpsOverlayInteractor
+
+    @Before
+    fun setup() {
+        testScope = TestScope(StandardTestDispatcher())
+        interactor = SideFpsOverlayInteractorImpl(fingerprintRepository)
+    }
+
+    @Test
+    fun testGetOverlayOffsets() =
+        testScope.runTest {
+            fingerprintRepository.setProperties(
+                sensorId = 1,
+                strength = SensorStrength.STRONG,
+                sensorType = FingerprintSensorType.REAR,
+                sensorLocations =
+                    mapOf(
+                        "" to
+                            SensorLocationInternal(
+                                "" /* displayId */,
+                                540 /* sensorLocationX */,
+                                1636 /* sensorLocationY */,
+                                130 /* sensorRadius */
+                            ),
+                        "display_id_1" to
+                            SensorLocationInternal(
+                                "display_id_1" /* displayId */,
+                                100 /* sensorLocationX */,
+                                300 /* sensorLocationY */,
+                                20 /* sensorRadius */
+                            )
+                    )
+            )
+
+            var offsets = interactor.getOverlayOffsets("display_id_1")
+            assertThat(offsets.displayId).isEqualTo("display_id_1")
+            assertThat(offsets.sensorLocationX).isEqualTo(100)
+            assertThat(offsets.sensorLocationY).isEqualTo(300)
+            assertThat(offsets.sensorRadius).isEqualTo(20)
+
+            offsets = interactor.getOverlayOffsets("invalid_display_id")
+            assertThat(offsets.displayId).isEqualTo("")
+            assertThat(offsets.sensorLocationX).isEqualTo(540)
+            assertThat(offsets.sensorLocationY).isEqualTo(1636)
+            assertThat(offsets.sensorRadius).isEqualTo(130)
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationLayoutEngineTest.java b/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationLayoutEngineTest.java
index a1d4fb4..69d8d0b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationLayoutEngineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationLayoutEngineTest.java
@@ -30,6 +30,7 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.complication.ComplicationLayoutEngine.Margins;
 import com.android.systemui.touch.TouchInsetManager;
 
 import org.junit.Before;
@@ -42,6 +43,7 @@
 
 import java.util.Arrays;
 import java.util.List;
+import java.util.Random;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
@@ -54,6 +56,14 @@
     @Mock
     TouchInsetManager.TouchInsetSession mTouchSession;
 
+    ComplicationLayoutEngine createComplicationLayoutEngine() {
+        return createComplicationLayoutEngine(0);
+    }
+
+    ComplicationLayoutEngine createComplicationLayoutEngine(int spacing) {
+        return new ComplicationLayoutEngine(mLayout, spacing, 0, 0, 0, 0, mTouchSession, 0, 0);
+    }
+
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
@@ -104,6 +114,73 @@
         engine.addComplication(info.id, info.view, info.lp, info.category);
     }
 
+    @Test
+    public void testCombineMargins() {
+        final Random rand = new Random();
+        final Margins margins1 = new Margins(rand.nextInt(), rand.nextInt(), rand.nextInt(),
+                rand.nextInt());
+        final Margins margins2 = new Margins(rand.nextInt(), rand.nextInt(), rand.nextInt(),
+                rand.nextInt());
+        final Margins combined = Margins.combine(margins1, margins2);
+        assertThat(margins1.start + margins2.start).isEqualTo(combined.start);
+        assertThat(margins1.top + margins2.top).isEqualTo(combined.top);
+        assertThat(margins1.end + margins2.end).isEqualTo(combined.end);
+        assertThat(margins1.bottom + margins2.bottom).isEqualTo(combined.bottom);
+    }
+
+    @Test
+    public void testComplicationMarginPosition() {
+        final Random rand = new Random();
+        final int startMargin = rand.nextInt();
+        final int topMargin = rand.nextInt();
+        final int endMargin = rand.nextInt();
+        final int bottomMargin = rand.nextInt();
+        final int spacing = rand.nextInt();
+
+        final ComplicationLayoutEngine engine = new ComplicationLayoutEngine(mLayout, spacing,
+                startMargin, topMargin, endMargin, bottomMargin, mTouchSession, 0, 0);
+
+        final ViewInfo firstViewInfo = new ViewInfo(
+                new ComplicationLayoutParams(
+                        100,
+                        100,
+                        ComplicationLayoutParams.POSITION_TOP
+                                | ComplicationLayoutParams.POSITION_END,
+                        ComplicationLayoutParams.DIRECTION_DOWN,
+                        0),
+                Complication.CATEGORY_SYSTEM,
+                mLayout);
+
+        addComplication(engine, firstViewInfo);
+        firstViewInfo.clearInvocations();
+
+        final ViewInfo secondViewInfo = new ViewInfo(
+                new ComplicationLayoutParams(
+                        100,
+                        100,
+                        ComplicationLayoutParams.POSITION_TOP
+                                | ComplicationLayoutParams.POSITION_END,
+                        ComplicationLayoutParams.DIRECTION_DOWN,
+                        0),
+                Complication.CATEGORY_STANDARD,
+                mLayout);
+
+        addComplication(engine, secondViewInfo);
+
+
+        // The first added view should have margins from both directions from the corner position.
+        verifyChange(firstViewInfo, false, lp -> {
+            assertThat(lp.topMargin).isEqualTo(topMargin);
+            assertThat(lp.getMarginEnd()).isEqualTo(endMargin);
+        });
+
+        // The second view should be spaced below the first view and have the side end margin.
+        verifyChange(secondViewInfo, false, lp -> {
+            assertThat(lp.topMargin).isEqualTo(spacing);
+            assertThat(lp.getMarginEnd()).isEqualTo(endMargin);
+        });
+    }
+
     /**
      * Makes sure the engine properly places a view within the {@link ConstraintLayout}.
      */
@@ -120,8 +197,7 @@
                 Complication.CATEGORY_STANDARD,
                 mLayout);
 
-        final ComplicationLayoutEngine engine =
-                new ComplicationLayoutEngine(mLayout, 0, mTouchSession, 0, 0);
+        final ComplicationLayoutEngine engine = createComplicationLayoutEngine();
         addComplication(engine, firstViewInfo);
 
         // Ensure the view is added to the top end corner
@@ -148,8 +224,7 @@
                 Complication.CATEGORY_STANDARD,
                 mLayout);
 
-        final ComplicationLayoutEngine engine =
-                new ComplicationLayoutEngine(mLayout, 0, mTouchSession, 0, 0);
+        final ComplicationLayoutEngine engine = createComplicationLayoutEngine();
         addComplication(engine, firstViewInfo);
 
         // Ensure the view is added to the top end corner
@@ -165,8 +240,7 @@
      */
     @Test
     public void testDirectionLayout() {
-        final ComplicationLayoutEngine engine =
-                new ComplicationLayoutEngine(mLayout, 0, mTouchSession, 0, 0);
+        final ComplicationLayoutEngine engine = createComplicationLayoutEngine();
 
         final ViewInfo firstViewInfo = new ViewInfo(
                 new ComplicationLayoutParams(
@@ -214,8 +288,7 @@
      */
     @Test
     public void testPositionLayout() {
-        final ComplicationLayoutEngine engine =
-                new ComplicationLayoutEngine(mLayout, 0, mTouchSession, 0, 0);
+        final ComplicationLayoutEngine engine = createComplicationLayoutEngine();
 
         final ViewInfo firstViewInfo = new ViewInfo(
                 new ComplicationLayoutParams(
@@ -302,8 +375,7 @@
     @Test
     public void testDefaultMargin() {
         final int margin = 5;
-        final ComplicationLayoutEngine engine =
-                new ComplicationLayoutEngine(mLayout, margin, mTouchSession, 0, 0);
+        final ComplicationLayoutEngine engine = createComplicationLayoutEngine(margin);
 
         final ViewInfo firstViewInfo = new ViewInfo(
                 new ComplicationLayoutParams(
@@ -379,8 +451,7 @@
     public void testComplicationMargin() {
         final int defaultMargin = 5;
         final int complicationMargin = 10;
-        final ComplicationLayoutEngine engine =
-                new ComplicationLayoutEngine(mLayout, defaultMargin, mTouchSession, 0, 0);
+        final ComplicationLayoutEngine engine = createComplicationLayoutEngine(defaultMargin);
 
         final ViewInfo firstViewInfo = new ViewInfo(
                 new ComplicationLayoutParams(
@@ -446,8 +517,7 @@
     @Test
     public void testWidthConstraint() {
         final int maxWidth = 20;
-        final ComplicationLayoutEngine engine =
-                new ComplicationLayoutEngine(mLayout, 0, mTouchSession, 0, 0);
+        final ComplicationLayoutEngine engine = createComplicationLayoutEngine();
 
         final ViewInfo viewStartDirection = new ViewInfo(
                 new ComplicationLayoutParams(
@@ -495,8 +565,7 @@
     @Test
     public void testHeightConstraint() {
         final int maxHeight = 20;
-        final ComplicationLayoutEngine engine =
-                new ComplicationLayoutEngine(mLayout, 0, mTouchSession, 0, 0);
+        final ComplicationLayoutEngine engine = createComplicationLayoutEngine();
 
         final ViewInfo viewUpDirection = new ViewInfo(
                 new ComplicationLayoutParams(
@@ -543,8 +612,7 @@
      */
     @Test
     public void testConstraintNotSetWhenNotSpecified() {
-        final ComplicationLayoutEngine engine =
-                new ComplicationLayoutEngine(mLayout, 0, mTouchSession, 0, 0);
+        final ComplicationLayoutEngine engine = createComplicationLayoutEngine();
 
         final ViewInfo view = new ViewInfo(
                 new ComplicationLayoutParams(
@@ -572,8 +640,7 @@
      */
     @Test
     public void testRemoval() {
-        final ComplicationLayoutEngine engine =
-                new ComplicationLayoutEngine(mLayout, 0, mTouchSession, 0, 0);
+        final ComplicationLayoutEngine engine = createComplicationLayoutEngine();
 
         final ViewInfo firstViewInfo = new ViewInfo(
                 new ComplicationLayoutParams(
@@ -619,8 +686,7 @@
      */
     @Test
     public void testDoubleRemoval() {
-        final ComplicationLayoutEngine engine =
-                new ComplicationLayoutEngine(mLayout, 0, mTouchSession, 0, 0);
+        final ComplicationLayoutEngine engine = createComplicationLayoutEngine();
 
         final ViewInfo firstViewInfo = new ViewInfo(
                 new ComplicationLayoutParams(
@@ -649,8 +715,7 @@
 
     @Test
     public void testGetViews() {
-        final ComplicationLayoutEngine engine =
-                new ComplicationLayoutEngine(mLayout, 0, mTouchSession, 0, 0);
+        final ComplicationLayoutEngine engine = createComplicationLayoutEngine();
 
         final ViewInfo topEndView = new ViewInfo(
                 new ComplicationLayoutParams(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationLayoutParamsTest.java b/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationLayoutParamsTest.java
index 286972d..a23e9e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationLayoutParamsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationLayoutParamsTest.java
@@ -112,7 +112,7 @@
                 ComplicationLayoutParams.POSITION_TOP,
                 ComplicationLayoutParams.DIRECTION_DOWN,
                 3);
-        assertThat(params.getMargin(10) == 10).isTrue();
+        assertThat(params.getDirectionalSpacing(10) == 10).isTrue();
     }
 
     /**
@@ -127,7 +127,7 @@
                 ComplicationLayoutParams.DIRECTION_DOWN,
                 3,
                 10);
-        assertThat(params.getMargin(5) == 10).isTrue();
+        assertThat(params.getDirectionalSpacing(5) == 10).isTrue();
     }
 
     /**
@@ -148,7 +148,7 @@
         assertThat(copy.getDirection() == params.getDirection()).isTrue();
         assertThat(copy.getPosition() == params.getPosition()).isTrue();
         assertThat(copy.getWeight() == params.getWeight()).isTrue();
-        assertThat(copy.getMargin(0) == params.getMargin(1)).isTrue();
+        assertThat(copy.getDirectionalSpacing(0) == params.getDirectionalSpacing(1)).isTrue();
         assertThat(copy.getConstraint() == params.getConstraint()).isTrue();
         assertThat(copy.height == params.height).isTrue();
         assertThat(copy.width == params.width).isTrue();
@@ -171,7 +171,7 @@
         assertThat(copy.getDirection() == params.getDirection()).isTrue();
         assertThat(copy.getPosition() == params.getPosition()).isTrue();
         assertThat(copy.getWeight() == params.getWeight()).isTrue();
-        assertThat(copy.getMargin(1) == params.getMargin(1)).isTrue();
+        assertThat(copy.getDirectionalSpacing(1) == params.getDirectionalSpacing(1)).isTrue();
         assertThat(copy.height == params.height).isTrue();
         assertThat(copy.width == params.width).isTrue();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index 3552399..494e230 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -301,6 +301,22 @@
     }
 
     @Test
+    public void test_onSensor_tap() {
+        mTriggers.onSensor(DozeLog.REASON_SENSOR_TAP, 100, 200, null);
+
+        verify(mHost).onSlpiTap(100, 200);
+        verify(mMachine).wakeUp(DozeLog.REASON_SENSOR_TAP);
+    }
+
+    @Test
+    public void test_onSensor_double_tap() {
+        mTriggers.onSensor(DozeLog.REASON_SENSOR_DOUBLE_TAP, 100, 200, null);
+
+        verify(mHost).onSlpiTap(100, 200);
+        verify(mMachine).wakeUp(DozeLog.REASON_SENSOR_DOUBLE_TAP);
+    }
+
+    @Test
     public void testPickupGestureDroppedKeyguardOccluded() {
         // GIVEN device is in doze (screen blank, but running doze sensors)
         when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 8f58140..83c89f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -52,10 +52,13 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.jank.InteractionJankMonitor;
+import com.android.internal.logging.InstanceId;
+import com.android.internal.logging.UiEventLogger;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardDisplayManager;
 import com.android.keyguard.KeyguardSecurityView;
 import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.keyguard.mediator.ScreenOnCoordinator;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.SysuiTestCase;
@@ -68,6 +71,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.log.SessionTracker;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
@@ -77,6 +81,7 @@
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.phone.BiometricUnlockController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -94,6 +99,8 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -137,12 +144,16 @@
     private @Mock AuthController mAuthController;
     private @Mock ShadeExpansionStateManager mShadeExpansionStateManager;
     private @Mock ShadeWindowLogger mShadeWindowLogger;
+    private @Captor ArgumentCaptor<KeyguardUpdateMonitorCallback>
+            mKeyguardUpdateMonitorCallbackCaptor;
     private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
     private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
 
     private FalsingCollectorFake mFalsingCollector;
 
     private @Mock CentralSurfaces mCentralSurfaces;
+    private @Mock UiEventLogger mUiEventLogger;
+    private @Mock SessionTracker mSessionTracker;
 
     private FakeFeatureFlags mFeatureFlags;
 
@@ -173,6 +184,24 @@
     }
 
     @Test
+    public void onLockdown_showKeyguard_evenIfKeyguardIsNotEnabledExternally() {
+        // GIVEN keyguard is not enabled and isn't showing
+        mViewMediator.onSystemReady();
+        mViewMediator.setKeyguardEnabled(false);
+        TestableLooper.get(this).processAllMessages();
+        captureKeyguardUpdateMonitorCallback();
+        assertFalse(mViewMediator.isShowingAndNotOccluded());
+
+        // WHEN lockdown occurs
+        when(mLockPatternUtils.isUserInLockdown(anyInt())).thenReturn(true);
+        mKeyguardUpdateMonitorCallbackCaptor.getValue().onStrongAuthStateChanged(0);
+
+        // THEN keyguard is shown
+        TestableLooper.get(this).processAllMessages();
+        assertTrue(mViewMediator.isShowingAndNotOccluded());
+    }
+
+    @Test
     public void testOnGoingToSleep_UpdatesKeyguardGoingAway() {
         mViewMediator.onStartedGoingToSleep(OFF_BECAUSE_OF_USER);
         verify(mUpdateMonitor).dispatchKeyguardGoingAway(false);
@@ -543,9 +572,26 @@
         assertTrue(mViewMediator.isShowingAndNotOccluded());
     }
 
+    @Test
+    public void testOnStartedWakingUp_logsUiEvent() {
+        final InstanceId instanceId = InstanceId.fakeInstanceId(8);
+        when(mSessionTracker.getSessionId((anyInt()))).thenReturn(instanceId);
+        mViewMediator.onStartedWakingUp(PowerManager.WAKE_REASON_LIFT, false);
+
+        verify(mUiEventLogger).logWithInstanceIdAndPosition(
+                eq(BiometricUnlockController.BiometricUiEvent.STARTED_WAKING_UP),
+                anyInt(),
+                any(),
+                eq(instanceId),
+                eq(PowerManager.WAKE_REASON_LIFT)
+        );
+    }
+
     private void createAndStartViewMediator() {
         mViewMediator = new KeyguardViewMediator(
                 mContext,
+                mUiEventLogger,
+                mSessionTracker,
                 mUserTracker,
                 mFalsingCollector,
                 mLockPatternUtils,
@@ -579,4 +625,8 @@
 
         mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null);
     }
+
+    private void captureKeyguardUpdateMonitorCallback() {
+        verify(mUpdateMonitor).registerCallback(mKeyguardUpdateMonitorCallbackCaptor.capture());
+    }
 }
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 d73c2c7..e61620b 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
@@ -530,7 +530,6 @@
                 keyguardRepository.setWakefulnessModel(
                     WakefulnessModel(
                         state = WakefulnessState.STARTING_TO_SLEEP,
-                        isWakingUpOrAwake = false,
                         lastWakeReason = WakeSleepReason.OTHER,
                         lastSleepReason = WakeSleepReason.OTHER,
                     )
@@ -545,7 +544,6 @@
                 keyguardRepository.setWakefulnessModel(
                     WakefulnessModel(
                         state = WakefulnessState.ASLEEP,
-                        isWakingUpOrAwake = false,
                         lastWakeReason = WakeSleepReason.OTHER,
                         lastSleepReason = WakeSleepReason.OTHER,
                     )
@@ -682,7 +680,6 @@
             keyguardRepository.setWakefulnessModel(
                 WakefulnessModel(
                     WakefulnessState.STARTING_TO_SLEEP,
-                    isWakingUpOrAwake = false,
                     lastWakeReason = WakeSleepReason.POWER_BUTTON,
                     lastSleepReason = WakeSleepReason.POWER_BUTTON
                 )
@@ -708,7 +705,6 @@
             keyguardRepository.setWakefulnessModel(
                 WakefulnessModel(
                     WakefulnessState.ASLEEP,
-                    isWakingUpOrAwake = false,
                     lastWakeReason = WakeSleepReason.POWER_BUTTON,
                     lastSleepReason = WakeSleepReason.POWER_BUTTON
                 )
@@ -765,7 +761,6 @@
                 keyguardRepository.setWakefulnessModel(
                     WakefulnessModel(
                         state = WakefulnessState.STARTING_TO_SLEEP,
-                        isWakingUpOrAwake = false,
                         lastWakeReason = WakeSleepReason.OTHER,
                         lastSleepReason = WakeSleepReason.OTHER,
                     )
@@ -1006,7 +1001,6 @@
         keyguardRepository.setWakefulnessModel(
             WakefulnessModel(
                 WakefulnessState.STARTING_TO_WAKE,
-                true,
                 WakeSleepReason.OTHER,
                 WakeSleepReason.OTHER
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt
index 657ee20..b3104b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt
@@ -50,7 +50,6 @@
         val testCoroutineScope = TestCoroutineScope()
         underTest =
             KeyguardBouncerRepositoryImpl(
-                viewMediatorCallback,
                 systemClock,
                 testCoroutineScope,
                 bouncerLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 4b4c7e9..4b797cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -27,7 +27,6 @@
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.common.shared.model.Position
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.doze.DozeHost
 import com.android.systemui.doze.DozeMachine
 import com.android.systemui.doze.DozeTransitionCallback
 import com.android.systemui.doze.DozeTransitionListener
@@ -69,7 +68,6 @@
 class KeyguardRepositoryImplTest : SysuiTestCase() {
 
     @Mock private lateinit var statusBarStateController: StatusBarStateController
-    @Mock private lateinit var dozeHost: DozeHost
     @Mock private lateinit var keyguardStateController: KeyguardStateController
     @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
     @Mock private lateinit var biometricUnlockController: BiometricUnlockController
@@ -91,7 +89,6 @@
         underTest =
             KeyguardRepositoryImpl(
                 statusBarStateController,
-                dozeHost,
                 wakefulnessLifecycle,
                 biometricUnlockController,
                 keyguardStateController,
@@ -262,42 +259,21 @@
     @Test
     fun isDozing() =
         testScope.runTest {
-            var latest: Boolean? = null
-            val job = underTest.isDozing.onEach { latest = it }.launchIn(this)
+            underTest.setIsDozing(true)
+            assertThat(underTest.isDozing.value).isEqualTo(true)
 
-            runCurrent()
-            val captor = argumentCaptor<DozeHost.Callback>()
-            verify(dozeHost).addCallback(captor.capture())
-
-            captor.value.onDozingChanged(true)
-            runCurrent()
-            assertThat(latest).isTrue()
-
-            captor.value.onDozingChanged(false)
-            runCurrent()
-            assertThat(latest).isFalse()
-
-            job.cancel()
-            runCurrent()
-            verify(dozeHost).removeCallback(captor.value)
+            underTest.setIsDozing(false)
+            assertThat(underTest.isDozing.value).isEqualTo(false)
         }
 
     @Test
     fun isDozing_startsWithCorrectInitialValueForIsDozing() =
         testScope.runTest {
-            var latest: Boolean? = null
+            assertThat(underTest.lastDozeTapToWakePosition.value).isEqualTo(null)
 
-            whenever(statusBarStateController.isDozing).thenReturn(true)
-            var job = underTest.isDozing.onEach { latest = it }.launchIn(this)
-            runCurrent()
-            assertThat(latest).isTrue()
-            job.cancel()
-
-            whenever(statusBarStateController.isDozing).thenReturn(false)
-            job = underTest.isDozing.onEach { latest = it }.launchIn(this)
-            runCurrent()
-            assertThat(latest).isFalse()
-            job.cancel()
+            val expectedPoint = Point(100, 200)
+            underTest.setLastDozeTapToWakePosition(expectedPoint)
+            assertThat(underTest.lastDozeTapToWakePosition.value).isEqualTo(expectedPoint)
         }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
index 2180a8f..ca6b8d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
@@ -18,7 +18,6 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.keyguard.ViewMediatorCallback
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
@@ -39,7 +38,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.Mockito.mock
 import org.mockito.MockitoAnnotations
 
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -62,7 +60,6 @@
         MockitoAnnotations.initMocks(this)
         bouncerRepository =
             KeyguardBouncerRepositoryImpl(
-                mock(ViewMediatorCallback::class.java),
                 FakeSystemClock(),
                 TestCoroutineScope(),
                 bouncerLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 4b7c641..3336e3b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -313,7 +313,7 @@
     @Test
     fun quickAffordance_bottomStartAffordanceHiddenWhileDozing() =
         testScope.runTest {
-            repository.setDozing(true)
+            repository.setIsDozing(true)
             homeControls.setState(
                 KeyguardQuickAffordanceConfig.LockScreenState.Visible(
                     icon = ICON,
@@ -348,7 +348,7 @@
     fun quickAffordanceAlwaysVisible_evenWhenLockScreenNotShowingAndDozing() =
         testScope.runTest {
             repository.setKeyguardShowing(false)
-            repository.setDozing(true)
+            repository.setIsDozing(true)
             val configKey = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
             homeControls.setState(
                 KeyguardQuickAffordanceConfig.LockScreenState.Visible(
@@ -623,7 +623,7 @@
         testScope.runTest {
             featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
             dockManager.setIsDocked(false)
-            val firstUseLongPress by collectLastValue (underTest.useLongPress())
+            val firstUseLongPress by collectLastValue(underTest.useLongPress())
             runCurrent()
 
             assertThat(firstUseLongPress).isTrue()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 344df0ac..603f199 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -871,7 +871,6 @@
     private fun startingToWake() =
         WakefulnessModel(
             WakefulnessState.STARTING_TO_WAKE,
-            true,
             WakeSleepReason.OTHER,
             WakeSleepReason.OTHER
         )
@@ -879,7 +878,6 @@
     private fun startingToSleep() =
         WakefulnessModel(
             WakefulnessState.STARTING_TO_SLEEP,
-            true,
             WakeSleepReason.OTHER,
             WakeSleepReason.OTHER
         )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 5eec8a8..69d43af 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -505,9 +505,9 @@
             val value = collectLastValue(underTest.isOverlayContainerVisible)
 
             assertThat(value()).isTrue()
-            repository.setDozing(true)
+            repository.setIsDozing(true)
             assertThat(value()).isFalse()
-            repository.setDozing(false)
+            repository.setIsDozing(false)
             assertThat(value()).isTrue()
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index 21a7a34..425d0bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -39,6 +39,7 @@
 import android.util.FeatureFlagUtils;
 import android.view.View;
 
+import androidx.annotation.NonNull;
 import androidx.test.filters.MediumTest;
 
 import com.android.internal.logging.UiEventLogger;
@@ -64,6 +65,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
+import java.util.function.Consumer;
 
 @MediumTest
 @RunWith(AndroidTestingRunner.class)
@@ -89,7 +91,7 @@
     private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
     private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
     private final MediaMetadata mMediaMetadata = mock(MediaMetadata.class);
-    private final MediaDescription  mMediaDescription = mock(MediaDescription.class);
+    private final MediaDescription mMediaDescription = mock(MediaDescription.class);
     private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock(
             NearbyMediaDevicesManager.class);
     private final AudioManager mAudioManager = mock(AudioManager.class);
@@ -102,6 +104,11 @@
     private MediaOutputController mMediaOutputController;
     private final List<String> mFeatures = new ArrayList<>();
 
+    @Override
+    protected boolean shouldFailOnLeakedReceiver() {
+        return true;
+    }
+
     @Before
     public void setUp() {
         when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager);
@@ -120,8 +127,7 @@
                 Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
                 mKeyguardManager, mFlags);
         mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
-        mMediaOutputDialog = new MediaOutputDialog(mContext, false, mBroadcastSender,
-                mMediaOutputController, mUiEventLogger);
+        mMediaOutputDialog = makeTestDialog(mMediaOutputController);
         mMediaOutputDialog.show();
 
         when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice);
@@ -130,7 +136,7 @@
 
     @After
     public void tearDown() {
-        mMediaOutputDialog.dismissDialog();
+        mMediaOutputDialog.dismiss();
     }
 
     @Test
@@ -311,11 +317,9 @@
         MediaOutputController mockMediaOutputController = mock(MediaOutputController.class);
         when(mockMediaOutputController.isBroadcastSupported()).thenReturn(false);
 
-        MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false, mBroadcastSender,
-                mockMediaOutputController, mUiEventLogger);
-        testDialog.show();
-
-        assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText);
+        withTestDialog(mockMediaOutputController, testDialog -> {
+            assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText);
+        });
     }
 
     @Test
@@ -328,11 +332,9 @@
         when(mockMediaOutputController.isBluetoothLeDevice(any())).thenReturn(true);
         when(mockMediaOutputController.isPlaying()).thenReturn(true);
         when(mockMediaOutputController.isBluetoothLeBroadcastEnabled()).thenReturn(false);
-        MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false, mBroadcastSender,
-                mockMediaOutputController, mUiEventLogger);
-        testDialog.show();
-
-        assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText);
+        withTestDialog(mockMediaOutputController, testDialog -> {
+            assertThat(testDialog.getStopButtonText().toString()).isEqualTo(stopText);
+        });
     }
 
     @Test
@@ -341,11 +343,9 @@
         when(mockMediaOutputController.isBroadcastSupported()).thenReturn(false);
         when(mockMediaOutputController.getCurrentConnectedMediaDevice()).thenReturn(null);
         when(mockMediaOutputController.isPlaying()).thenReturn(false);
-        MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false, mBroadcastSender,
-                mockMediaOutputController, mUiEventLogger);
-        testDialog.show();
-
-        testDialog.onStopButtonClick();
+        withTestDialog(mockMediaOutputController, testDialog -> {
+            testDialog.onStopButtonClick();
+        });
 
         verify(mockMediaOutputController).releaseSession();
     }
@@ -354,13 +354,22 @@
     // Check the visibility metric logging by creating a new MediaOutput dialog,
     // and verify if the calling times increases.
     public void onCreate_ShouldLogVisibility() {
-        MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false, mBroadcastSender,
-                mMediaOutputController, mUiEventLogger);
-        testDialog.show();
-
-        testDialog.dismissDialog();
+        withTestDialog(mMediaOutputController, testDialog -> {});
 
         verify(mUiEventLogger, times(2))
                 .log(MediaOutputDialog.MediaOutputEvent.MEDIA_OUTPUT_DIALOG_SHOW);
     }
+
+    @NonNull
+    private MediaOutputDialog makeTestDialog(MediaOutputController controller) {
+        return new MediaOutputDialog(mContext, false, mBroadcastSender,
+                controller, mUiEventLogger);
+    }
+
+    private void withTestDialog(MediaOutputController controller, Consumer<MediaOutputDialog> c) {
+        MediaOutputDialog testDialog = makeTestDialog(controller);
+        testDialog.show();
+        c.accept(testDialog);
+        testDialog.dismiss();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index e4d8b25..810ab34 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -145,7 +145,8 @@
         mMainExecutor = new FakeExecutor(new FakeSystemClock());
 
         mSharedPreferencesByUser = new SparseArray<>();
-        when(mTileLifecycleManagerFactory.create(any(Intent.class), any(UserHandle.class)))
+        when(mTileLifecycleManagerFactory
+                .create(any(Intent.class), any(UserHandle.class)))
                 .thenReturn(mTileLifecycleManager);
         when(mUserFileManager.getSharedPreferences(anyString(), anyInt(), anyInt()))
                 .thenAnswer((Answer<SharedPreferences>) invocation -> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
index 2e6b0cf..67587e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
@@ -60,6 +60,8 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.After;
 import org.junit.Before;
@@ -81,6 +83,7 @@
     private ComponentName mTileServiceComponentName;
     private Intent mTileServiceIntent;
     private UserHandle mUser;
+    private FakeExecutor mExecutor;
     private HandlerThread mThread;
     private Handler mHandler;
     private TileLifecycleManager mStateManager;
@@ -109,12 +112,14 @@
         mThread = new HandlerThread("TestThread");
         mThread.start();
         mHandler = Handler.createAsync(mThread.getLooper());
+        mExecutor = new FakeExecutor(new FakeSystemClock());
         mStateManager = new TileLifecycleManager(mHandler, mWrappedContext,
                 mock(IQSService.class),
                 mMockPackageManagerAdapter,
                 mMockBroadcastDispatcher,
                 mTileServiceIntent,
-                mUser);
+                mUser,
+                mExecutor);
     }
 
     @After
@@ -152,7 +157,8 @@
 
     @Test
     public void testBind() {
-        mStateManager.setBindService(true);
+        mStateManager.executeSetBindService(true);
+        mExecutor.runAllReady();
         verifyBind(1);
     }
 
@@ -160,7 +166,8 @@
     public void testPackageReceiverExported() throws Exception {
         // Make sure that we register a receiver
         setPackageEnabled(false);
-        mStateManager.setBindService(true);
+        mStateManager.executeSetBindService(true);
+        mExecutor.runAllReady();
         IntentFilter filter = mWrappedContext.mLastIntentFilter;
         assertTrue(filter.hasAction(Intent.ACTION_PACKAGE_ADDED));
         assertTrue(filter.hasAction(Intent.ACTION_PACKAGE_CHANGED));
@@ -170,14 +177,17 @@
 
     @Test
     public void testUnbind() {
-        mStateManager.setBindService(true);
-        mStateManager.setBindService(false);
+        mStateManager.executeSetBindService(true);
+        mExecutor.runAllReady();
+        mStateManager.executeSetBindService(false);
+        mExecutor.runAllReady();
         assertFalse(mContext.isBound(mTileServiceComponentName));
     }
 
     @Test
     public void testTileServiceCallbacks() throws Exception {
-        mStateManager.setBindService(true);
+        mStateManager.executeSetBindService(true);
+        mExecutor.runAllReady();
         mStateManager.onTileAdded();
         verify(mMockTileService).onTileAdded();
         mStateManager.onStartListening();
@@ -193,7 +203,8 @@
     @Test
     public void testAddedBeforeBind() throws Exception {
         mStateManager.onTileAdded();
-        mStateManager.setBindService(true);
+        mStateManager.executeSetBindService(true);
+        mExecutor.runAllReady();
 
         verifyBind(1);
         verify(mMockTileService).onTileAdded();
@@ -203,7 +214,8 @@
     public void testListeningBeforeBind() throws Exception {
         mStateManager.onTileAdded();
         mStateManager.onStartListening();
-        mStateManager.setBindService(true);
+        mStateManager.executeSetBindService(true);
+        mExecutor.runAllReady();
 
         verifyBind(1);
         verify(mMockTileService).onTileAdded();
@@ -215,7 +227,8 @@
         mStateManager.onTileAdded();
         mStateManager.onStartListening();
         mStateManager.onClick(null);
-        mStateManager.setBindService(true);
+        mStateManager.executeSetBindService(true);
+        mExecutor.runAllReady();
 
         verifyBind(1);
         verify(mMockTileService).onTileAdded();
@@ -228,10 +241,12 @@
         mStateManager.onTileAdded();
         mStateManager.onStartListening();
         mStateManager.onStopListening();
-        mStateManager.setBindService(true);
+        mStateManager.executeSetBindService(true);
+        mExecutor.runAllReady();
 
         verifyBind(1);
-        mStateManager.setBindService(false);
+        mStateManager.executeSetBindService(false);
+        mExecutor.runAllReady();
         assertFalse(mContext.isBound(mTileServiceComponentName));
         verify(mMockTileService, never()).onStartListening();
     }
@@ -242,10 +257,12 @@
         mStateManager.onStartListening();
         mStateManager.onClick(null);
         mStateManager.onStopListening();
-        mStateManager.setBindService(true);
+        mStateManager.executeSetBindService(true);
+        mExecutor.runAllReady();
 
         verifyBind(1);
-        mStateManager.setBindService(false);
+        mStateManager.executeSetBindService(false);
+        mExecutor.runAllReady();
         assertFalse(mContext.isBound(mTileServiceComponentName));
         verify(mMockTileService, never()).onClick(null);
     }
@@ -255,7 +272,8 @@
         mStateManager.onTileAdded();
         mStateManager.onStartListening();
         setPackageEnabled(false);
-        mStateManager.setBindService(true);
+        mStateManager.executeSetBindService(true);
+        mExecutor.runAllReady();
         // Package not available, not yet created.
         verifyBind(0);
 
@@ -267,18 +285,19 @@
                         Intent.ACTION_PACKAGE_CHANGED,
                         Uri.fromParts(
                                 "package", mTileServiceComponentName.getPackageName(), null)));
+        mExecutor.runAllReady();
         verifyBind(1);
     }
 
     @Test
     public void testKillProcess() throws Exception {
         mStateManager.onStartListening();
-        mStateManager.setBindService(true);
+        mStateManager.executeSetBindService(true);
+        mExecutor.runAllReady();
         mStateManager.setBindRetryDelay(0);
+        mExecutor.runAllReady();
         mStateManager.onServiceDisconnected(mTileServiceComponentName);
-
-        // Guarantees mHandler has processed all messages.
-        assertTrue(mHandler.runWithScissors(()->{}, TEST_FAIL_TIMEOUT));
+        mExecutor.runAllReady();
 
         // Two calls: one for the first bind, one for the restart.
         verifyBind(2);
@@ -299,9 +318,11 @@
                 mMockPackageManagerAdapter,
                 mMockBroadcastDispatcher,
                 mTileServiceIntent,
-                mUser);
+                mUser,
+                mExecutor);
 
-        manager.setBindService(true);
+        manager.executeSetBindService(true);
+        mExecutor.runAllReady();
 
         ArgumentCaptor<ServiceConnection> captor = ArgumentCaptor.forClass(ServiceConnection.class);
         verify(falseContext).bindServiceAsUser(any(), captor.capture(), anyInt(), any());
@@ -318,9 +339,11 @@
                 mMockPackageManagerAdapter,
                 mMockBroadcastDispatcher,
                 mTileServiceIntent,
-                mUser);
+                mUser,
+                mExecutor);
 
-        manager.setBindService(true);
+        manager.executeSetBindService(true);
+        mExecutor.runAllReady();
         int flags = Context.BIND_AUTO_CREATE
                 | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE
                 | Context.BIND_WAIVE_PRIORITY;
@@ -337,9 +360,11 @@
                 mMockPackageManagerAdapter,
                 mMockBroadcastDispatcher,
                 mTileServiceIntent,
-                mUser);
+                mUser,
+                mExecutor);
 
-        manager.setBindService(true);
+        manager.executeSetBindService(true);
+        mExecutor.runAllReady();
         int flags = Context.BIND_AUTO_CREATE
                 | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE
                 | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
index 9ca7a85..28331bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
@@ -187,7 +187,7 @@
         mTileServiceManager.setBindAllowed(true);
 
         ArgumentCaptor<Boolean> captor = ArgumentCaptor.forClass(Boolean.class);
-        verify(mTileLifecycle, times(1)).setBindService(captor.capture());
+        verify(mTileLifecycle, times(1)).executeSetBindService(captor.capture());
         assertTrue((boolean) captor.getValue());
 
         mTileServiceManager.setBindRequested(false);
@@ -198,7 +198,7 @@
 
         mTileServiceManager.setBindAllowed(false);
         captor = ArgumentCaptor.forClass(Boolean.class);
-        verify(mTileLifecycle, times(2)).setBindService(captor.capture());
+        verify(mTileLifecycle, times(2)).executeSetBindService(captor.capture());
         assertFalse((boolean) captor.getValue());
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index 12b5656..4bc16a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -48,6 +48,9 @@
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.After;
 import org.junit.Assert;
@@ -118,7 +121,8 @@
 
         mTileService = new TestTileServices(mQSHost, provider, mBroadcastDispatcher,
                 mUserTracker, mKeyguardStateController, mCommandQueue, mStatusBarIconController,
-                mPanelInteractor, mCustomTileAddedRepository);
+                mPanelInteractor, mCustomTileAddedRepository,
+                new FakeExecutor(new FakeSystemClock()));
     }
 
     @After
@@ -297,10 +301,10 @@
                 BroadcastDispatcher broadcastDispatcher, UserTracker userTracker,
                 KeyguardStateController keyguardStateController, CommandQueue commandQueue,
                 StatusBarIconController statusBarIconController, PanelInteractor panelInteractor,
-                CustomTileAddedRepository customTileAddedRepository) {
+                CustomTileAddedRepository customTileAddedRepository, DelayableExecutor executor) {
             super(host, handlerProvider, broadcastDispatcher, userTracker, keyguardStateController,
                     commandQueue, statusBarIconController, panelInteractor,
-                    customTileAddedRepository);
+                    customTileAddedRepository, executor);
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
index b089e38..b00ae39 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
@@ -93,6 +93,8 @@
     private static final String CARD_DESCRIPTION = "•••• 1234";
     private static final Icon CARD_IMAGE =
             Icon.createWithBitmap(Bitmap.createBitmap(70, 50, Bitmap.Config.ARGB_8888));
+    private static final int PRIMARY_USER_ID = 0;
+    private static final int SECONDARY_USER_ID = 10;
 
     private final Drawable mTileIcon = mContext.getDrawable(R.drawable.ic_qs_wallet);
     private final Intent mWalletIntent = new Intent(QuickAccessWalletService.ACTION_VIEW_WALLET)
@@ -120,6 +122,8 @@
     private SecureSettings mSecureSettings;
     @Mock
     private QuickAccessWalletController mController;
+    @Mock
+    private Icon mCardImage;
     @Captor
     ArgumentCaptor<QuickAccessWalletClient.OnWalletCardsRetrievedCallback> mCallbackCaptor;
 
@@ -142,6 +146,8 @@
         when(mQuickAccessWalletClient.isWalletServiceAvailable()).thenReturn(true);
         when(mQuickAccessWalletClient.isWalletFeatureAvailableWhenDeviceLocked()).thenReturn(true);
         when(mController.getWalletClient()).thenReturn(mQuickAccessWalletClient);
+        when(mCardImage.getType()).thenReturn(Icon.TYPE_URI);
+        when(mCardImage.loadDrawableAsUser(any(), eq(SECONDARY_USER_ID))).thenReturn(null);
 
         mTile = new QuickAccessWalletTile(
                 mHost,
@@ -382,6 +388,28 @@
     }
 
     @Test
+    public void testQueryCards_notCurrentUser_hasCards_noSideViewDrawable() {
+        when(mKeyguardStateController.isUnlocked()).thenReturn(true);
+
+        PendingIntent pendingIntent =
+                PendingIntent.getActivity(mContext, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE);
+        WalletCard walletCard =
+                new WalletCard.Builder(
+                    CARD_ID, mCardImage, CARD_DESCRIPTION, pendingIntent).build();
+        GetWalletCardsResponse response =
+                new GetWalletCardsResponse(Collections.singletonList(walletCard), 0);
+
+        mTile.handleSetListening(true);
+
+        verify(mController).queryWalletCards(mCallbackCaptor.capture());
+
+        mCallbackCaptor.getValue().onWalletCardsRetrieved(response);
+        mTestableLooper.processAllMessages();
+
+        assertNull(mTile.getState().sideViewCustomDrawable);
+    }
+
+    @Test
     public void testQueryCards_noCards_notUpdateSideViewDrawable() {
         setUpWalletCard(/* hasCard= */ false);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
index 0e2a3ac..190ee81 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
@@ -24,13 +24,13 @@
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_OPEN;
-import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM;
 import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
 import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
 import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -76,7 +76,7 @@
                 .addChange(TRANSIT_CLOSE, 0 /* flags */,
                         createTaskInfo(2 /* taskId */, ACTIVITY_TYPE_STANDARD))
                 .addChange(TRANSIT_OPEN, FLAG_IS_WALLPAPER, null /* taskInfo */)
-                .addChange(TRANSIT_CHANGE, FLAG_FIRST_CUSTOM, null /* taskInfo */)
+                .addChange(TRANSIT_CHANGE, FLAG_IS_DIVIDER_BAR, null /* taskInfo */)
                 .build();
         // Check apps extraction
         RemoteAnimationTarget[] wrapped = RemoteAnimationTargetCompat.wrapApps(combined,
@@ -107,7 +107,7 @@
         RemoteAnimationTarget[] nonApps = RemoteAnimationTargetCompat.wrapNonApps(combined,
                 false /* wallpapers */, mock(SurfaceControl.Transaction.class), null /* leashes */);
         assertEquals(1, nonApps.length);
-        assertTrue(nonApps[0].prefixOrderIndex < closeLayer);
+        assertTrue(nonApps[0].prefixOrderIndex == Integer.MAX_VALUE);
         assertEquals(MODE_CHANGING, nonApps[0].mode);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
index c7ea09c..2e5afa4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
@@ -340,6 +340,11 @@
     }
 
     public void setConnectivityViaCallbackInNetworkController(
+            Network network, NetworkCapabilities networkCapabilities) {
+        mDefaultCallbackInNetworkController.onCapabilitiesChanged(network, networkCapabilities);
+    }
+
+    public void setConnectivityViaCallbackInNetworkController(
             int networkType, boolean validated, boolean isConnected, WifiInfo wifiInfo) {
         final NetworkCapabilities.Builder builder =
                 new NetworkCapabilities.Builder(mNetCapabilities);
@@ -351,6 +356,13 @@
                 mock(Network.class), builder.build());
     }
 
+    public void setConnectivityViaDefaultAndNormalCallbackInWifiTracker(
+            Network network, NetworkCapabilities networkCapabilities) {
+        mNetworkCallback.onAvailable(network);
+        mNetworkCallback.onCapabilitiesChanged(network, networkCapabilities);
+        mDefaultCallbackInWifiTracker.onCapabilitiesChanged(network, networkCapabilities);
+    }
+
     public void setConnectivityViaCallbackInWifiTracker(
             int networkType, boolean validated, boolean isConnected, WifiInfo wifiInfo) {
         final NetworkCapabilities.Builder builder =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
index 68170ea..44a1c50 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.statusbar.connectivity;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+
 import static junit.framework.Assert.assertEquals;
 
 import static org.junit.Assert.assertTrue;
@@ -25,6 +28,7 @@
 
 import android.content.Intent;
 import android.net.ConnectivityManager;
+import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
 import android.net.vcn.VcnTransportInfo;
@@ -43,6 +47,8 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
 
+import java.util.Collections;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
@@ -269,6 +275,83 @@
         }
     }
 
+    /** Test for b/225902574. */
+    @Test
+    public void vcnOnlyOnUnderlyingNetwork() {
+        setWifiEnabled(true);
+
+        // Set up a carrier merged network...
+        WifiInfo underlyingCarrierMergedInfo = Mockito.mock(WifiInfo.class);
+        when(underlyingCarrierMergedInfo.isCarrierMerged()).thenReturn(true);
+        when(underlyingCarrierMergedInfo.isPrimary()).thenReturn(true);
+        int zeroLevel = 0;
+        when(underlyingCarrierMergedInfo.getRssi()).thenReturn(calculateRssiForLevel(zeroLevel));
+
+        NetworkCapabilities underlyingNetworkCapabilities = Mockito.mock(NetworkCapabilities.class);
+        when(underlyingNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR))
+                .thenReturn(true);
+        when(underlyingNetworkCapabilities.getTransportInfo())
+                .thenReturn(underlyingCarrierMergedInfo);
+
+        Network underlyingNetwork = Mockito.mock(Network.class);
+        when(mMockCm.getNetworkCapabilities(underlyingNetwork))
+                .thenReturn(underlyingNetworkCapabilities);
+
+        NetworkCapabilities.Builder mainCapabilitiesBuilder = new NetworkCapabilities.Builder();
+        mainCapabilitiesBuilder.addTransportType(TRANSPORT_CELLULAR);
+        mainCapabilitiesBuilder.setTransportInfo(null);
+        // And make the carrier merged network the underlying network, *not* the main network.
+        mainCapabilitiesBuilder.setUnderlyingNetworks(Collections.singletonList(underlyingNetwork));
+
+        Network primaryNetwork = Mockito.mock(Network.class);
+        int primaryNetworkId = 1;
+        when(primaryNetwork.getNetId()).thenReturn(primaryNetworkId);
+
+        // WHEN this primary network with underlying carrier merged information is sent
+        setConnectivityViaDefaultAndNormalCallbackInWifiTracker(
+                primaryNetwork, mainCapabilitiesBuilder.build());
+
+        // THEN we see the mobile data indicators for carrier merged
+        verifyLastMobileDataIndicatorsForVcn(
+                /* visible= */ true,
+                /* level= */ zeroLevel,
+                TelephonyIcons.ICON_CWF,
+                /* inet= */ false);
+
+        // For each level...
+        for (int testLevel = 0; testLevel < WifiIcons.WIFI_LEVEL_COUNT; testLevel++) {
+            int rssi = calculateRssiForLevel(testLevel);
+            when(underlyingCarrierMergedInfo.getRssi()).thenReturn(rssi);
+            // WHEN the new level is sent to the callbacks
+            setConnectivityViaDefaultAndNormalCallbackInWifiTracker(
+                    primaryNetwork, mainCapabilitiesBuilder.build());
+
+            // WHEN the network is validated
+            mainCapabilitiesBuilder.addCapability(NET_CAPABILITY_VALIDATED);
+            setConnectivityViaCallbackInNetworkController(
+                    primaryNetwork, mainCapabilitiesBuilder.build());
+
+            // THEN we see the mobile data indicators with inet=true (no exclamation mark)
+            verifyLastMobileDataIndicatorsForVcn(
+                    /* visible= */ true,
+                    testLevel,
+                    TelephonyIcons.ICON_CWF,
+                    /* inet= */ true);
+
+            // WHEN the network is not validated
+            mainCapabilitiesBuilder.removeCapability(NET_CAPABILITY_VALIDATED);
+            setConnectivityViaCallbackInNetworkController(
+                    primaryNetwork, mainCapabilitiesBuilder.build());
+
+            // THEN we see the mobile data indicators with inet=false (exclamation mark)
+            verifyLastMobileDataIndicatorsForVcn(
+                    /* visible= */ true,
+                    testLevel,
+                    TelephonyIcons.ICON_CWF,
+                    /* inet= */ false);
+        }
+    }
+
     @Test
     public void testDisableWiFiWithVcnWithUnderlyingWifi() {
         String testSsid = "Test VCN SSID";
@@ -290,11 +373,7 @@
     }
 
     protected void setWifiLevel(int level) {
-        float amountPerLevel = (MAX_RSSI - MIN_RSSI) / (WifiIcons.WIFI_LEVEL_COUNT - 1);
-        int rssi = (int) (MIN_RSSI + level * amountPerLevel);
-        // Put RSSI in the middle of the range.
-        rssi += amountPerLevel / 2;
-        when(mWifiInfo.getRssi()).thenReturn(rssi);
+        when(mWifiInfo.getRssi()).thenReturn(calculateRssiForLevel(level));
         setConnectivityViaCallbackInWifiTracker(
                 NetworkCapabilities.TRANSPORT_WIFI, false, true, mWifiInfo);
     }
@@ -312,19 +391,23 @@
     }
 
     protected void setWifiLevelForVcn(int level) {
-        float amountPerLevel = (MAX_RSSI - MIN_RSSI) / (WifiIcons.WIFI_LEVEL_COUNT - 1);
-        int rssi = (int) (MIN_RSSI + level * amountPerLevel);
-        // Put RSSI in the middle of the range.
-        rssi += amountPerLevel / 2;
         when(mVcnTransportInfo.getWifiInfo()).thenReturn(mWifiInfo);
         when(mVcnTransportInfo.makeCopy(anyLong())).thenReturn(mVcnTransportInfo);
-        when(mWifiInfo.getRssi()).thenReturn(rssi);
+        when(mWifiInfo.getRssi()).thenReturn(calculateRssiForLevel(level));
         when(mWifiInfo.isCarrierMerged()).thenReturn(true);
         when(mWifiInfo.getSubscriptionId()).thenReturn(1);
         setConnectivityViaCallbackInWifiTrackerForVcn(
                 NetworkCapabilities.TRANSPORT_CELLULAR, false, true, mVcnTransportInfo);
     }
 
+    private int calculateRssiForLevel(int level) {
+        float amountPerLevel = (MAX_RSSI - MIN_RSSI) / (WifiIcons.WIFI_LEVEL_COUNT - 1);
+        int rssi = (int) (MIN_RSSI + level * amountPerLevel);
+        // Put RSSI in the middle of the range.
+        rssi += amountPerLevel / 2;
+        return rssi;
+    }
+
     protected void setWifiStateForVcn(boolean connected, String ssid) {
         when(mVcnTransportInfo.getWifiInfo()).thenReturn(mWifiInfo);
         when(mVcnTransportInfo.makeCopy(anyLong())).thenReturn(mVcnTransportInfo);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt
index 746544a..d3f5ade 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt
@@ -118,7 +118,10 @@
         assertThat(oldCallbackNotified).isFalse()
     }
 
-    inner class TestGestureDetector : GenericGestureDetector("fakeTag", displayTracker) {
+    inner class TestGestureDetector : GenericGestureDetector(
+            "fakeTag",
+            displayTracker.defaultDisplayId
+    ) {
         var isGestureListening = false
 
         override fun onInputEvent(ev: InputEvent) {
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 c3f5123..2fbe871 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
@@ -327,7 +327,7 @@
     fun unseenNotificationIsMarkedAsSeenWhenKeyguardGoesAway() {
         // GIVEN: Keyguard is showing, not dozing, unseen notification is present
         keyguardRepository.setKeyguardShowing(true)
-        keyguardRepository.setDozing(false)
+        keyguardRepository.setIsDozing(false)
         runKeyguardCoordinatorTest {
             val fakeEntry = NotificationEntryBuilder().build()
             collectionListener.onEntryAdded(fakeEntry)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
index b6b28c9..4a30800 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
@@ -17,6 +17,7 @@
 import android.app.PendingIntent
 import android.content.Intent
 import android.os.RemoteException
+import android.os.UserHandle
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardUpdateMonitor
@@ -102,6 +103,7 @@
                 activityIntentHelper,
                 mainExecutor,
             )
+        whenever(userTracker.userHandle).thenReturn(UserHandle.OWNER)
     }
 
     @Test
@@ -150,11 +152,28 @@
 
     @Test
     fun postStartActivityDismissingKeyguard_intent_postsOnMain() {
+        whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
         val intent = mock(Intent::class.java)
 
         underTest.postStartActivityDismissingKeyguard(intent, 0)
 
         assertThat(mainExecutor.numPending()).isEqualTo(1)
+        mainExecutor.runAllReady()
+
+        verify(deviceProvisionedController).isDeviceProvisioned
+        verify(shadeController).runPostCollapseRunnables()
+    }
+
+    @Test
+    fun postStartActivityDismissingKeyguard_intent_notDeviceProvisioned_doesNotProceed() {
+        whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(false)
+        val intent = mock(Intent::class.java)
+
+        underTest.postStartActivityDismissingKeyguard(intent, 0)
+        mainExecutor.runAllReady()
+
+        verify(deviceProvisionedController).isDeviceProvisioned
+        verify(shadeController, never()).runPostCollapseRunnables()
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
index 163369f..57037e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
@@ -25,8 +25,10 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
+import android.graphics.Point;
 import android.os.PowerManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
@@ -42,6 +44,7 @@
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.keyguard.domain.interactor.BurnInInteractor;
+import com.android.systemui.keyguard.domain.interactor.DozeInteractor;
 import com.android.systemui.shade.NotificationShadeWindowViewController;
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -95,6 +98,7 @@
     @Mock private DozeHost.Callback mCallback;
     @Mock private BurnInInteractor mBurnInInteractor;
 
+    @Mock private DozeInteractor mDozeInteractor;
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
@@ -104,7 +108,7 @@
                 () -> mAssistManager, mDozeScrimController,
                 mKeyguardUpdateMonitor, mPulseExpansionHandler,
                 mNotificationShadeWindowController, mNotificationWakeUpCoordinator,
-                mAuthController, mNotificationIconAreaController,
+                mAuthController, mNotificationIconAreaController, mDozeInteractor,
                 mBurnInInteractor);
 
         mDozeServiceHost.initialize(
@@ -216,6 +220,19 @@
         assertFalse(mDozeServiceHost.isPulsePending());
         verify(mDozeScrimController).pulseOutNow();
     }
+
+    @Test
+    public void onSlpiTap_calls_DozeInteractor() {
+        mDozeServiceHost.onSlpiTap(100, 200);
+        verify(mDozeInteractor).setLastTapToWakePosition(new Point(100, 200));
+    }
+
+    @Test
+    public void onSlpiTap_doesnt_pass_negative_values() {
+        mDozeServiceHost.onSlpiTap(-1, 200);
+        mDozeServiceHost.onSlpiTap(100, -2);
+        verifyZeroInteractions(mDozeInteractor);
+    }
     @Test
     public void dozeTimeTickSentTBurnInInteractor() {
         // WHEN dozeTimeTick
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 10efcd4..9b1d93b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -74,6 +74,7 @@
 import com.android.systemui.statusbar.phone.StatusBarLocationPublisher;
 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
+import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.FakeCollapsedStatusBarViewBinder;
 import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.FakeCollapsedStatusBarViewModel;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.window.StatusBarWindowStateController;
@@ -129,6 +130,7 @@
     @Mock
     private StatusBarIconController.DarkIconManager mIconManager;
     private FakeCollapsedStatusBarViewModel mCollapsedStatusBarViewModel;
+    private FakeCollapsedStatusBarViewBinder mCollapsedStatusBarViewBinder;
     @Mock
     private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
     @Mock
@@ -612,6 +614,55 @@
         assertEquals(View.VISIBLE, getClockView().getVisibility());
     }
 
+    @Test
+    public void testStatusBarIcons_hiddenThroughoutLockscreenToDreamTransition() {
+        final CollapsedStatusBarFragment fragment = resumeAndGetFragment();
+
+        // WHEN a transition to dream has started
+        mCollapsedStatusBarViewBinder.getListener().onTransitionFromLockscreenToDreamStarted();
+        when(mKeyguardUpdateMonitor.isDreaming()).thenReturn(true);
+        when(mKeyguardStateController.isOccluded()).thenReturn(true);
+        fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
+
+        // THEN status icons should be invisible or gone, but certainly not VISIBLE.
+        assertNotEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+        assertNotEquals(View.VISIBLE, getClockView().getVisibility());
+
+        // WHEN the transition has finished and dream is displaying
+        mockLockscreenToDreamTransitionFinished();
+        // (This approximates "dream is displaying")
+        when(mStatusBarHideIconsForBouncerManager.getShouldHideStatusBarIconsForBouncer())
+                .thenReturn(true);
+        fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
+
+        // THEN the views still aren't visible because dream is hiding them
+        assertNotEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+        assertNotEquals(View.VISIBLE, getClockView().getVisibility());
+
+        // WHEN dream has ended
+        when(mStatusBarHideIconsForBouncerManager.getShouldHideStatusBarIconsForBouncer())
+                .thenReturn(false);
+        fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
+
+        // THEN the views can be visible again
+        assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+        assertEquals(View.VISIBLE, getClockView().getVisibility());
+    }
+
+    @Test
+    public void testStatusBarIcons_lockscreenToDreamTransitionButNotDreaming_iconsVisible() {
+        final CollapsedStatusBarFragment fragment = resumeAndGetFragment();
+
+        // WHEN a transition to dream has started but we're *not* dreaming
+        mCollapsedStatusBarViewBinder.getListener().onTransitionFromLockscreenToDreamStarted();
+        when(mKeyguardUpdateMonitor.isDreaming()).thenReturn(false);
+        fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
+
+        // THEN the views are still visible
+        assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
+        assertEquals(View.VISIBLE, getClockView().getVisibility());
+    }
+
     @Override
     protected Fragment instantiate(Context context, String className, Bundle arguments) {
         MockitoAnnotations.initMocks(this);
@@ -631,6 +682,7 @@
 
         mShadeExpansionStateManager = new ShadeExpansionStateManager();
         mCollapsedStatusBarViewModel = new FakeCollapsedStatusBarViewModel();
+        mCollapsedStatusBarViewBinder = new FakeCollapsedStatusBarViewBinder();
 
         setUpNotificationIconAreaController();
         return new CollapsedStatusBarFragment(
@@ -644,6 +696,7 @@
                 mStatusBarIconController,
                 mIconManagerFactory,
                 mCollapsedStatusBarViewModel,
+                mCollapsedStatusBarViewBinder,
                 mStatusBarHideIconsForBouncerManager,
                 mKeyguardStateController,
                 mShadeViewController,
@@ -718,6 +771,12 @@
         }
     }
 
+    private void mockLockscreenToDreamTransitionFinished() {
+        for (StatusBarWindowStateListener listener : mStatusBarWindowStateListeners) {
+            listener.onStatusBarWindowStateChanged(StatusBarManager.WINDOW_STATE_HIDDEN);
+        }
+    }
+
     private CollapsedStatusBarFragment resumeAndGetFragment() {
         mFragments.dispatchResume();
         processAllMessages();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
index bde05b9..5a887eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.log.table.TableLogBufferFactory
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository
@@ -131,6 +132,7 @@
                 context,
                 IMMEDIATE,
                 scope,
+                FakeAirplaneModeRepository(),
                 wifiRepository,
                 mock(),
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index 7cc59b6..38c7432e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -34,13 +34,16 @@
 import android.telephony.TelephonyCallback
 import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
 import android.telephony.TelephonyManager
+import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.telephony.PhoneConstants
 import com.android.settingslib.R
 import com.android.settingslib.mobile.MobileMappings
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.log.table.TableLogBufferFactory
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigRepository
@@ -51,25 +54,25 @@
 import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlots
 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl
+import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import java.util.UUID
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.cancel
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.yield
-import org.junit.After
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
 import org.junit.Assert.assertThrows
 import org.junit.Assert.assertTrue
 import org.junit.Before
@@ -83,6 +86,9 @@
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
+// This is required because our [SubscriptionManager.OnSubscriptionsChangedListener] uses a looper
+// to run the callback and this makes the looper place nicely with TestScope etc.
+@TestableLooper.RunWithLooper
 class MobileConnectionsRepositoryTest : SysuiTestCase() {
     private lateinit var underTest: MobileConnectionsRepositoryImpl
 
@@ -90,7 +96,8 @@
     private lateinit var carrierMergedFactory: CarrierMergedConnectionRepository.Factory
     private lateinit var fullConnectionFactory: FullMobileConnectionRepository.Factory
     private lateinit var connectivityRepository: ConnectivityRepository
-    private lateinit var wifiRepository: FakeWifiRepository
+    private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
+    private lateinit var wifiRepository: WifiRepository
     private lateinit var carrierConfigRepository: CarrierConfigRepository
     @Mock private lateinit var connectivityManager: ConnectivityManager
     @Mock private lateinit var subscriptionManager: SubscriptionManager
@@ -102,7 +109,8 @@
     private val mobileMappings = FakeMobileMappingsProxy()
     private val subscriptionManagerProxy = FakeSubscriptionManagerProxy()
 
-    private val scope = CoroutineScope(IMMEDIATE)
+    private val dispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(dispatcher)
 
     @Before
     fun setUp() {
@@ -138,11 +146,23 @@
                 context,
                 mock(),
                 mock(),
-                scope,
+                testScope.backgroundScope,
                 mock(),
             )
 
-        wifiRepository = FakeWifiRepository()
+        airplaneModeRepository = FakeAirplaneModeRepository()
+
+        wifiRepository =
+            WifiRepositoryImpl(
+                fakeBroadcastDispatcher,
+                connectivityManager,
+                connectivityRepository,
+                mock(),
+                mock(),
+                FakeExecutor(FakeSystemClock()),
+                testScope.backgroundScope,
+                mock(),
+            )
 
         carrierConfigRepository =
             CarrierConfigRepository(
@@ -150,28 +170,28 @@
                 mock(),
                 mock(),
                 logger,
-                scope,
+                testScope.backgroundScope,
             )
 
         connectionFactory =
             MobileConnectionRepositoryImpl.Factory(
                 fakeBroadcastDispatcher,
                 telephonyManager = telephonyManager,
-                bgDispatcher = IMMEDIATE,
+                bgDispatcher = dispatcher,
                 logger = logger,
                 mobileMappingsProxy = mobileMappings,
-                scope = scope,
+                scope = testScope.backgroundScope,
                 carrierConfigRepository = carrierConfigRepository,
             )
         carrierMergedFactory =
             CarrierMergedConnectionRepository.Factory(
                 telephonyManager,
-                scope,
+                testScope.backgroundScope,
                 wifiRepository,
             )
         fullConnectionFactory =
             FullMobileConnectionRepository.Factory(
-                scope = scope,
+                scope = testScope.backgroundScope,
                 logFactory = logBufferFactory,
                 mobileRepoFactory = connectionFactory,
                 carrierMergedRepoFactory = carrierMergedFactory,
@@ -188,46 +208,38 @@
                 mobileMappings,
                 fakeBroadcastDispatcher,
                 context,
-                IMMEDIATE,
-                scope,
+                dispatcher,
+                testScope.backgroundScope,
+                airplaneModeRepository,
                 wifiRepository,
                 fullConnectionFactory,
             )
-    }
 
-    @After
-    fun tearDown() {
-        scope.cancel()
+        testScope.runCurrent()
     }
 
     @Test
     fun testSubscriptions_initiallyEmpty() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             assertThat(underTest.subscriptions.value).isEqualTo(listOf<SubscriptionModel>())
         }
 
     @Test
     fun testSubscriptions_listUpdates() =
-        runBlocking(IMMEDIATE) {
-            var latest: List<SubscriptionModel>? = null
-
-            val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
+        testScope.runTest {
+            val latest by collectLastValue(underTest.subscriptions)
 
             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
                 .thenReturn(listOf(SUB_1, SUB_2))
             getSubscriptionCallback().onSubscriptionsChanged()
 
             assertThat(latest).isEqualTo(listOf(MODEL_1, MODEL_2))
-
-            job.cancel()
         }
 
     @Test
     fun testSubscriptions_removingSub_updatesList() =
-        runBlocking(IMMEDIATE) {
-            var latest: List<SubscriptionModel>? = null
-
-            val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
+        testScope.runTest {
+            val latest by collectLastValue(underTest.subscriptions)
 
             // WHEN 2 networks show up
             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
@@ -241,71 +253,55 @@
 
             // THEN the subscriptions list represents the newest change
             assertThat(latest).isEqualTo(listOf(MODEL_2))
-
-            job.cancel()
         }
 
     @Test
     fun testSubscriptions_carrierMergedOnly_listHasCarrierMerged() =
-        runBlocking(IMMEDIATE) {
-            var latest: List<SubscriptionModel>? = null
+        testScope.runTest {
+            val latest by collectLastValue(underTest.subscriptions)
 
-            val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
-
-            wifiRepository.setWifiNetwork(WIFI_NETWORK_CM)
+            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
                 .thenReturn(listOf(SUB_CM))
             getSubscriptionCallback().onSubscriptionsChanged()
 
             assertThat(latest).isEqualTo(listOf(MODEL_CM))
-
-            job.cancel()
         }
 
     @Test
     fun testSubscriptions_carrierMergedAndOther_listHasBothWithCarrierMergedLast() =
-        runBlocking(IMMEDIATE) {
-            var latest: List<SubscriptionModel>? = null
+        testScope.runTest {
+            val latest by collectLastValue(underTest.subscriptions)
 
-            val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
-
-            wifiRepository.setWifiNetwork(WIFI_NETWORK_CM)
+            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
                 .thenReturn(listOf(SUB_1, SUB_2, SUB_CM))
             getSubscriptionCallback().onSubscriptionsChanged()
 
             assertThat(latest).isEqualTo(listOf(MODEL_1, MODEL_2, MODEL_CM))
-
-            job.cancel()
         }
 
     @Test
     fun testActiveDataSubscriptionId_initialValueIsNull() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             assertThat(underTest.activeMobileDataSubscriptionId.value).isEqualTo(null)
         }
 
     @Test
     fun testActiveDataSubscriptionId_updates() =
-        runBlocking(IMMEDIATE) {
-            var active: Int? = null
-
-            val job = underTest.activeMobileDataSubscriptionId.onEach { active = it }.launchIn(this)
+        testScope.runTest {
+            val active by collectLastValue(underTest.activeMobileDataSubscriptionId)
 
             getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
                 .onActiveDataSubscriptionIdChanged(SUB_2_ID)
 
             assertThat(active).isEqualTo(SUB_2_ID)
-
-            job.cancel()
         }
 
     @Test
     fun activeSubId_nullIfInvalidSubIdIsReceived() =
-        runBlocking(IMMEDIATE) {
-            var latest: Int? = null
-
-            val job = underTest.activeMobileDataSubscriptionId.onEach { latest = it }.launchIn(this)
+        testScope.runTest {
+            val latest by collectLastValue(underTest.activeMobileDataSubscriptionId)
 
             getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
                 .onActiveDataSubscriptionIdChanged(SUB_2_ID)
@@ -316,8 +312,6 @@
                 .onActiveDataSubscriptionIdChanged(INVALID_SUBSCRIPTION_ID)
 
             assertThat(latest).isNull()
-
-            job.cancel()
         }
 
     @Test
@@ -327,23 +321,19 @@
 
     @Test
     fun activeRepo_updatesWithActiveDataId() =
-        runBlocking(IMMEDIATE) {
-            var latest: MobileConnectionRepository? = null
-            val job = underTest.activeMobileDataRepository.onEach { latest = it }.launchIn(this)
+        testScope.runTest {
+            val latest by collectLastValue(underTest.activeMobileDataRepository)
 
             getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
                 .onActiveDataSubscriptionIdChanged(SUB_2_ID)
 
             assertThat(latest?.subId).isEqualTo(SUB_2_ID)
-
-            job.cancel()
         }
 
     @Test
     fun activeRepo_nullIfActiveDataSubIdBecomesInvalid() =
-        runBlocking(IMMEDIATE) {
-            var latest: MobileConnectionRepository? = null
-            val job = underTest.activeMobileDataRepository.onEach { latest = it }.launchIn(this)
+        testScope.runTest {
+            val latest by collectLastValue(underTest.activeMobileDataRepository)
 
             getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
                 .onActiveDataSubscriptionIdChanged(SUB_2_ID)
@@ -354,64 +344,49 @@
                 .onActiveDataSubscriptionIdChanged(INVALID_SUBSCRIPTION_ID)
 
             assertThat(latest).isNull()
-
-            job.cancel()
         }
 
     @Test
     /** Regression test for b/268146648. */
     fun activeSubIdIsSetBeforeSubscriptionsAreUpdated_doesNotThrow() =
-        runBlocking(IMMEDIATE) {
-            var activeRepo: MobileConnectionRepository? = null
-            var subscriptions: List<SubscriptionModel>? = null
-
-            val activeRepoJob =
-                underTest.activeMobileDataRepository.onEach { activeRepo = it }.launchIn(this)
-            val subscriptionsJob =
-                underTest.subscriptions.onEach { subscriptions = it }.launchIn(this)
+        testScope.runTest {
+            val activeRepo by collectLastValue(underTest.activeMobileDataRepository)
+            val subscriptions by collectLastValue(underTest.subscriptions)
 
             getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
                 .onActiveDataSubscriptionIdChanged(SUB_2_ID)
 
             assertThat(subscriptions).isEmpty()
             assertThat(activeRepo).isNotNull()
-
-            activeRepoJob.cancel()
-            subscriptionsJob.cancel()
         }
 
     @Test
     fun getRepoForSubId_activeDataSubIdIsRequestedBeforeSubscriptionsUpdate() =
-        runBlocking(IMMEDIATE) {
-            var latest: MobileConnectionRepository? = null
-            var subscriptions: List<SubscriptionModel>? = null
-            val activeSubIdJob =
-                underTest.activeMobileDataSubscriptionId
-                    .filterNotNull()
-                    .onEach { latest = underTest.getRepoForSubId(it) }
-                    .launchIn(this)
-            val subscriptionsJob =
-                underTest.subscriptions.onEach { subscriptions = it }.launchIn(this)
+        testScope.runTest {
+            var latestActiveRepo: MobileConnectionRepository? = null
+            collectLastValue(
+                underTest.activeMobileDataSubscriptionId.filterNotNull().onEach {
+                    latestActiveRepo = underTest.getRepoForSubId(it)
+                }
+            )
+
+            val latestSubscriptions by collectLastValue(underTest.subscriptions)
 
             // Active data subscription id is sent, but no subscription change has been posted yet
             getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
                 .onActiveDataSubscriptionIdChanged(SUB_2_ID)
 
             // Subscriptions list is empty
-            assertThat(subscriptions).isEmpty()
+            assertThat(latestSubscriptions).isEmpty()
             // getRepoForSubId does not throw
-            assertThat(latest).isNotNull()
-
-            activeSubIdJob.cancel()
-            subscriptionsJob.cancel()
+            assertThat(latestActiveRepo).isNotNull()
         }
 
     @Test
     fun activeDataSentBeforeSubscriptionList_subscriptionReusesActiveDataRepo() =
-        runBlocking(IMMEDIATE) {
-            var activeRepo: MobileConnectionRepository? = null
-            val job = underTest.activeMobileDataRepository.onEach { activeRepo = it }.launchIn(this)
-            val subscriptionsJob = underTest.subscriptions.launchIn(this)
+        testScope.runTest {
+            val activeRepo by collectLastValue(underTest.activeMobileDataRepository)
+            collectLastValue(underTest.subscriptions)
 
             // GIVEN active repo is updated before the subscription list updates
             getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
@@ -429,15 +404,12 @@
 
             // THEN the newly request repo has been cached and reused
             assertThat(activeRepo).isSameInstanceAs(newRepo)
-
-            job.cancel()
-            subscriptionsJob.cancel()
         }
 
     @Test
     fun testConnectionRepository_validSubId_isCached() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.subscriptions.launchIn(this)
+        testScope.runTest {
+            collectLastValue(underTest.subscriptions)
 
             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
                 .thenReturn(listOf(SUB_1))
@@ -447,16 +419,15 @@
             val repo2 = underTest.getRepoForSubId(SUB_1_ID)
 
             assertThat(repo1).isSameInstanceAs(repo2)
-
-            job.cancel()
         }
 
     @Test
     fun testConnectionRepository_carrierMergedSubId_isCached() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.subscriptions.launchIn(this)
+        testScope.runTest {
+            collectLastValue(underTest.subscriptions)
 
-            wifiRepository.setWifiNetwork(WIFI_NETWORK_CM)
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
                 .thenReturn(listOf(SUB_CM))
             getSubscriptionCallback().onSubscriptionsChanged()
@@ -465,16 +436,15 @@
             val repo2 = underTest.getRepoForSubId(SUB_CM_ID)
 
             assertThat(repo1).isSameInstanceAs(repo2)
-
-            job.cancel()
         }
 
     @Test
     fun testConnectionRepository_carrierMergedAndMobileSubs_usesCorrectRepos() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.subscriptions.launchIn(this)
+        testScope.runTest {
+            collectLastValue(underTest.subscriptions)
 
-            wifiRepository.setWifiNetwork(WIFI_NETWORK_CM)
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
                 .thenReturn(listOf(SUB_1, SUB_CM))
             getSubscriptionCallback().onSubscriptionsChanged()
@@ -483,16 +453,15 @@
             val mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
             assertThat(carrierMergedRepo.getIsCarrierMerged()).isTrue()
             assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun testSubscriptions_subNoLongerCarrierMerged_repoUpdates() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.subscriptions.launchIn(this)
+        testScope.runTest {
+            collectLastValue(underTest.subscriptions)
 
-            wifiRepository.setWifiNetwork(WIFI_NETWORK_CM)
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
                 .thenReturn(listOf(SUB_1, SUB_CM))
             getSubscriptionCallback().onSubscriptionsChanged()
@@ -503,26 +472,28 @@
             assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
 
             // WHEN the wifi network updates to be not carrier merged
-            wifiRepository.setWifiNetwork(WifiNetworkModel.Active(networkId = 4, level = 1))
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE)
+            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE)
+            runCurrent()
 
             // THEN the repos update
             val noLongerCarrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
             mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
             assertThat(noLongerCarrierMergedRepo.getIsCarrierMerged()).isFalse()
             assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun testSubscriptions_subBecomesCarrierMerged_repoUpdates() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.subscriptions.launchIn(this)
+        testScope.runTest {
+            collectLastValue(underTest.subscriptions)
 
-            wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE)
+            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE)
             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
                 .thenReturn(listOf(SUB_1, SUB_CM))
             getSubscriptionCallback().onSubscriptionsChanged()
+            runCurrent()
 
             val notYetCarrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
             var mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
@@ -530,21 +501,21 @@
             assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
 
             // WHEN the wifi network updates to be carrier merged
-            wifiRepository.setWifiNetwork(WIFI_NETWORK_CM)
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+            runCurrent()
 
             // THEN the repos update
             val carrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
             mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
             assertThat(carrierMergedRepo.getIsCarrierMerged()).isTrue()
             assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun testConnectionCache_clearsInvalidSubscriptions() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.subscriptions.launchIn(this)
+        testScope.runTest {
+            collectLastValue(underTest.subscriptions)
 
             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
                 .thenReturn(listOf(SUB_1, SUB_2))
@@ -563,16 +534,15 @@
             getSubscriptionCallback().onSubscriptionsChanged()
 
             assertThat(underTest.getSubIdRepoCache()).containsExactly(SUB_1_ID, repo1)
-
-            job.cancel()
         }
 
     @Test
     fun testConnectionCache_clearsInvalidSubscriptions_includingCarrierMerged() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.subscriptions.launchIn(this)
+        testScope.runTest {
+            collectLastValue(underTest.subscriptions)
 
-            wifiRepository.setWifiNetwork(WIFI_NETWORK_CM)
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
                 .thenReturn(listOf(SUB_1, SUB_2, SUB_CM))
             getSubscriptionCallback().onSubscriptionsChanged()
@@ -591,15 +561,13 @@
             getSubscriptionCallback().onSubscriptionsChanged()
 
             assertThat(underTest.getSubIdRepoCache()).containsExactly(SUB_1_ID, repo1)
-
-            job.cancel()
         }
 
     /** Regression test for b/261706421 */
     @Test
     fun testConnectionsCache_clearMultipleSubscriptionsAtOnce_doesNotThrow() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.subscriptions.launchIn(this)
+        testScope.runTest {
+            collectLastValue(underTest.subscriptions)
 
             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
                 .thenReturn(listOf(SUB_1, SUB_2))
@@ -617,26 +585,20 @@
             getSubscriptionCallback().onSubscriptionsChanged()
 
             assertThat(underTest.getSubIdRepoCache()).isEmpty()
-
-            job.cancel()
         }
 
     @Test
     fun testConnectionRepository_invalidSubId_throws() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.subscriptions.launchIn(this)
-
+        testScope.runTest {
             assertThrows(IllegalArgumentException::class.java) {
                 underTest.getRepoForSubId(SUB_1_ID)
             }
-
-            job.cancel()
         }
 
     @Test
     fun connectionRepository_logBufferContainsSubIdInItsName() =
-        runBlocking(IMMEDIATE) {
-            val job = underTest.subscriptions.launchIn(this)
+        testScope.runTest {
+            collectLastValue(underTest.subscriptions)
 
             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
                 .thenReturn(listOf(SUB_1, SUB_2))
@@ -655,15 +617,12 @@
                     eq(tableBufferLogName(SUB_2_ID)),
                     anyInt(),
                 )
-
-            job.cancel()
         }
 
     @Test
     fun testDefaultDataSubId_updatesOnBroadcast() =
-        runBlocking(IMMEDIATE) {
-            var latest: Int? = null
-            val job = underTest.defaultDataSubId.onEach { latest = it }.launchIn(this)
+        testScope.runTest {
+            val latest by collectLastValue(underTest.defaultDataSubId)
 
             assertThat(latest).isEqualTo(INVALID_SUBSCRIPTION_ID)
 
@@ -686,28 +645,24 @@
             }
 
             assertThat(latest).isEqualTo(SUB_1_ID)
-
-            job.cancel()
         }
 
     @Test
     fun defaultDataSubId_fetchesInitialValueOnStart() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             subscriptionManagerProxy.defaultDataSubId = 2
-            var latest: Int? = null
-            val job = underTest.defaultDataSubId.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.defaultDataSubId)
 
             assertThat(latest).isEqualTo(2)
-
-            job.cancel()
         }
 
     @Test
     fun defaultDataSubId_fetchesCurrentOnRestart() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             subscriptionManagerProxy.defaultDataSubId = 2
             var latest: Int? = null
             var job = underTest.defaultDataSubId.onEach { latest = it }.launchIn(this)
+            runCurrent()
 
             assertThat(latest).isEqualTo(2)
 
@@ -720,6 +675,7 @@
             subscriptionManagerProxy.defaultDataSubId = 1
 
             job = underTest.defaultDataSubId.onEach { latest = it }.launchIn(this)
+            runCurrent()
 
             assertThat(latest).isEqualTo(1)
 
@@ -733,43 +689,37 @@
 
     @Test
     fun mobileIsDefault_capsHaveCellular_isDefault() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val caps =
                 mock<NetworkCapabilities>().also {
                     whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
                 }
 
-            var latest: Boolean? = null
-            val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.mobileIsDefault)
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
 
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun mobileIsDefault_capsDoNotHaveCellular_isNotDefault() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val caps =
                 mock<NetworkCapabilities>().also {
                     whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
                 }
 
-            var latest: Boolean? = null
-            val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.mobileIsDefault)
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
 
             assertThat(latest).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun mobileIsDefault_carrierMergedViaMobile_isDefault() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val carrierMergedInfo =
                 mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
             val caps =
@@ -778,151 +728,144 @@
                     whenever(it.transportInfo).thenReturn(carrierMergedInfo)
                 }
 
-            var latest: Boolean? = null
-            val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.mobileIsDefault)
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
 
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun mobileIsDefault_wifiDefault_mobileNotDefault() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val caps =
                 mock<NetworkCapabilities>().also {
                     whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
                 }
 
-            var latest: Boolean? = null
-            val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.mobileIsDefault)
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
 
             assertThat(latest).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun mobileIsDefault_ethernetDefault_mobileNotDefault() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val caps =
                 mock<NetworkCapabilities>().also {
                     whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(true)
                 }
 
-            var latest: Boolean? = null
-            val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.mobileIsDefault)
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
 
             assertThat(latest).isFalse()
-
-            job.cancel()
         }
 
     /** Regression test for b/272586234. */
     @Test
     fun hasCarrierMergedConnection_carrierMergedViaWifi_isTrue() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val carrierMergedInfo =
-                mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+                mock<WifiInfo>().apply {
+                    whenever(this.isCarrierMerged).thenReturn(true)
+                    whenever(this.isPrimary).thenReturn(true)
+                }
             val caps =
                 mock<NetworkCapabilities>().also {
                     whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
                     whenever(it.transportInfo).thenReturn(carrierMergedInfo)
                 }
 
-            var latest: Boolean? = null
-            val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.hasCarrierMergedConnection)
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
-            yield()
+            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
 
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun hasCarrierMergedConnection_carrierMergedViaMobile_isTrue() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val carrierMergedInfo =
-                mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+                mock<WifiInfo>().apply {
+                    whenever(this.isCarrierMerged).thenReturn(true)
+                    whenever(this.isPrimary).thenReturn(true)
+                }
             val caps =
                 mock<NetworkCapabilities>().also {
                     whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
                     whenever(it.transportInfo).thenReturn(carrierMergedInfo)
                 }
 
-            var latest: Boolean? = null
-            val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.hasCarrierMergedConnection)
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
-            yield()
+            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
 
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     /** Regression test for b/272586234. */
     @Test
     fun hasCarrierMergedConnection_carrierMergedViaWifiWithVcnTransport_isTrue() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val carrierMergedInfo =
-                mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+                mock<WifiInfo>().apply {
+                    whenever(this.isCarrierMerged).thenReturn(true)
+                    whenever(this.isPrimary).thenReturn(true)
+                }
             val caps =
                 mock<NetworkCapabilities>().also {
                     whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
                     whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
                 }
 
-            var latest: Boolean? = null
-            val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.hasCarrierMergedConnection)
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
-            yield()
+            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
 
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun hasCarrierMergedConnection_carrierMergedViaMobileWithVcnTransport_isTrue() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val carrierMergedInfo =
-                mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+                mock<WifiInfo>().apply {
+                    whenever(this.isCarrierMerged).thenReturn(true)
+                    whenever(this.isPrimary).thenReturn(true)
+                }
             val caps =
                 mock<NetworkCapabilities>().also {
                     whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
                     whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
                 }
 
-            var latest: Boolean? = null
-            val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.hasCarrierMergedConnection)
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
-            yield()
+            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
 
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun hasCarrierMergedConnection_isCarrierMergedViaUnderlyingWifi_isTrue() =
-        runBlocking(IMMEDIATE) {
-            var latest: Boolean? = null
-            val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this)
+        testScope.runTest {
+            val latest by collectLastValue(underTest.hasCarrierMergedConnection)
 
             val underlyingNetwork = mock<Network>()
             val carrierMergedInfo =
-                mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+                mock<WifiInfo>().apply {
+                    whenever(this.isCarrierMerged).thenReturn(true)
+                    whenever(this.isPrimary).thenReturn(true)
+                }
             val underlyingWifiCapabilities =
                 mock<NetworkCapabilities>().also {
                     whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
@@ -941,23 +884,23 @@
                 }
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
-            yield()
+            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
 
             // THEN there's a carrier merged connection
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun hasCarrierMergedConnection_isCarrierMergedViaUnderlyingCellular_isTrue() =
-        runBlocking(IMMEDIATE) {
-            var latest: Boolean? = null
-            val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this)
+        testScope.runTest {
+            val latest by collectLastValue(underTest.hasCarrierMergedConnection)
 
             val underlyingCarrierMergedNetwork = mock<Network>()
             val carrierMergedInfo =
-                mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+                mock<WifiInfo>().apply {
+                    whenever(this.isCarrierMerged).thenReturn(true)
+                    whenever(this.isPrimary).thenReturn(true)
+                }
             val underlyingCapabilities =
                 mock<NetworkCapabilities>().also {
                     whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
@@ -977,22 +920,19 @@
                 }
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
-            yield()
+            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
 
             // THEN there's a carrier merged connection
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     /** Regression test for b/272586234. */
     @Test
-    fun hasCarrierMergedConnection_defaultNotCarrierMerged_butWifiRepoHasCarrierMerged_isTrue() =
-        runBlocking(IMMEDIATE) {
-            var latest: Boolean? = null
-            val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this)
+    fun hasCarrierMergedConnection_defaultIsWifiNotCarrierMerged_wifiRepoIsCarrierMerged_isTrue() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.hasCarrierMergedConnection)
 
-            // WHEN the default callback isn't carrier merged
+            // WHEN the default callback is TRANSPORT_WIFI but not carrier merged
             val carrierMergedInfo =
                 mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(false) }
             val caps =
@@ -1001,16 +941,57 @@
                     whenever(it.transportInfo).thenReturn(carrierMergedInfo)
                 }
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
-            yield()
 
             // BUT the wifi repo has gotten updates that it *is* carrier merged
-            wifiRepository.setWifiNetwork(WIFI_NETWORK_CM)
-            yield()
+            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
 
             // THEN hasCarrierMergedConnection is true
             assertThat(latest).isTrue()
+        }
 
-            job.cancel()
+    /** Regression test for b/278618530. */
+    @Test
+    fun hasCarrierMergedConnection_defaultIsCellular_wifiRepoIsCarrierMerged_isFalse() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.hasCarrierMergedConnection)
+
+            // WHEN the default callback is TRANSPORT_CELLULAR and not carrier merged
+            val caps =
+                mock<NetworkCapabilities>().also {
+                    whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+                    whenever(it.transportInfo).thenReturn(null)
+                }
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+            // BUT the wifi repo has gotten updates that it *is* carrier merged
+            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+
+            // THEN hasCarrierMergedConnection is **false** (The default network being CELLULAR
+            // takes precedence over the wifi network being carrier merged.)
+            assertThat(latest).isFalse()
+        }
+
+    /** Regression test for b/278618530. */
+    @Test
+    fun hasCarrierMergedConnection_defaultCellular_wifiIsCarrierMerged_airplaneMode_isTrue() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.hasCarrierMergedConnection)
+
+            // WHEN the default callback is TRANSPORT_CELLULAR and not carrier merged
+            val caps =
+                mock<NetworkCapabilities>().also {
+                    whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+                    whenever(it.transportInfo).thenReturn(null)
+                }
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+            // BUT the wifi repo has gotten updates that it *is* carrier merged
+            getNormalNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+            // AND we're in airplane mode
+            airplaneModeRepository.setIsAirplaneMode(true)
+
+            // THEN hasCarrierMergedConnection is true.
+            assertThat(latest).isTrue()
         }
 
     @Test
@@ -1020,43 +1001,37 @@
 
     @Test
     fun defaultConnectionIsValidated_capsHaveValidated_isValidated() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val caps =
                 mock<NetworkCapabilities>().also {
                     whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(true)
                 }
 
-            var latest: Boolean? = null
-            val job = underTest.defaultConnectionIsValidated.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.defaultConnectionIsValidated)
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
 
             assertThat(latest).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun defaultConnectionIsValidated_capsHaveNotValidated_isNotValidated() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val caps =
                 mock<NetworkCapabilities>().also {
                     whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(false)
                 }
 
-            var latest: Boolean? = null
-            val job = underTest.defaultConnectionIsValidated.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.defaultConnectionIsValidated)
 
             getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
 
             assertThat(latest).isFalse()
-
-            job.cancel()
         }
 
     @Test
     fun config_initiallyFromContext() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             overrideResource(R.bool.config_showMin3G, true)
             val configFromContext = MobileMappings.Config.readConfig(context)
             assertThat(configFromContext.showAtLeast3G).isTrue()
@@ -1074,26 +1049,24 @@
                     mobileMappings,
                     fakeBroadcastDispatcher,
                     context,
-                    IMMEDIATE,
-                    scope,
+                    dispatcher,
+                    testScope.backgroundScope,
+                    airplaneModeRepository,
                     wifiRepository,
                     fullConnectionFactory,
                 )
 
-            var latest: MobileMappings.Config? = null
-            val job = underTest.defaultDataSubRatConfig.onEach { latest = it }.launchIn(this)
+            val latest by collectLastValue(underTest.defaultDataSubRatConfig)
 
             assertTrue(latest!!.areEqual(configFromContext))
             assertTrue(latest!!.showAtLeast3G)
-
-            job.cancel()
         }
 
     @Test
     fun config_subIdChangeEvent_updated() =
-        runBlocking(IMMEDIATE) {
-            var latest: MobileMappings.Config? = null
-            val job = underTest.defaultDataSubRatConfig.onEach { latest = it }.launchIn(this)
+        testScope.runTest {
+            val latest by collectLastValue(underTest.defaultDataSubRatConfig)
+
             assertThat(latest!!.showAtLeast3G).isFalse()
 
             overrideResource(R.bool.config_showMin3G, true)
@@ -1112,15 +1085,13 @@
             // THEN the config is updated
             assertTrue(latest!!.areEqual(configFromContext))
             assertTrue(latest!!.showAtLeast3G)
-
-            job.cancel()
         }
 
     @Test
     fun config_carrierConfigChangeEvent_updated() =
-        runBlocking(IMMEDIATE) {
-            var latest: MobileMappings.Config? = null
-            val job = underTest.defaultDataSubRatConfig.onEach { latest = it }.launchIn(this)
+        testScope.runTest {
+            val latest by collectLastValue(underTest.defaultDataSubRatConfig)
+
             assertThat(latest!!.showAtLeast3G).isFalse()
 
             overrideResource(R.bool.config_showMin3G, true)
@@ -1138,15 +1109,12 @@
             // THEN the config is updated
             assertThat(latest!!.areEqual(configFromContext)).isTrue()
             assertThat(latest!!.showAtLeast3G).isTrue()
-
-            job.cancel()
         }
 
     @Test
     fun activeDataChange_inSameGroup_emitsUnit() =
-        runBlocking(IMMEDIATE) {
-            var latest: Unit? = null
-            val job = underTest.activeSubChangedInGroupEvent.onEach { latest = it }.launchIn(this)
+        testScope.runTest {
+            val latest by collectLastValue(underTest.activeSubChangedInGroupEvent)
 
             getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
                 .onActiveDataSubscriptionIdChanged(SUB_3_ID_GROUPED)
@@ -1154,15 +1122,12 @@
                 .onActiveDataSubscriptionIdChanged(SUB_4_ID_GROUPED)
 
             assertThat(latest).isEqualTo(Unit)
-
-            job.cancel()
         }
 
     @Test
     fun activeDataChange_notInSameGroup_doesNotEmit() =
-        runBlocking(IMMEDIATE) {
-            var latest: Unit? = null
-            val job = underTest.activeSubChangedInGroupEvent.onEach { latest = it }.launchIn(this)
+        testScope.runTest {
+            val latest by collectLastValue(underTest.activeSubChangedInGroupEvent)
 
             getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
                 .onActiveDataSubscriptionIdChanged(SUB_3_ID_GROUPED)
@@ -1170,38 +1135,46 @@
                 .onActiveDataSubscriptionIdChanged(SUB_1_ID)
 
             assertThat(latest).isEqualTo(null)
-
-            job.cancel()
         }
 
-    private fun getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback {
+    private fun TestScope.getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback {
+        runCurrent()
         val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
         verify(connectivityManager).registerDefaultNetworkCallback(callbackCaptor.capture())
         return callbackCaptor.value!!
     }
 
-    private fun getSubscriptionCallback(): SubscriptionManager.OnSubscriptionsChangedListener {
+    // Note: This is used to update the [WifiRepository].
+    private fun TestScope.getNormalNetworkCallback(): ConnectivityManager.NetworkCallback {
+        runCurrent()
+        val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
+        verify(connectivityManager).registerNetworkCallback(any(), callbackCaptor.capture())
+        return callbackCaptor.value!!
+    }
+
+    private fun TestScope.getSubscriptionCallback():
+        SubscriptionManager.OnSubscriptionsChangedListener {
+        runCurrent()
         val callbackCaptor = argumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>()
         verify(subscriptionManager)
             .addOnSubscriptionsChangedListener(any(), callbackCaptor.capture())
         return callbackCaptor.value!!
     }
 
-    private fun getTelephonyCallbacks(): List<TelephonyCallback> {
+    private fun TestScope.getTelephonyCallbacks(): List<TelephonyCallback> {
+        runCurrent()
         val callbackCaptor = argumentCaptor<TelephonyCallback>()
         verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
         return callbackCaptor.allValues
     }
 
-    private inline fun <reified T> getTelephonyCallbackForType(): T {
-        val cbs = getTelephonyCallbacks().filterIsInstance<T>()
+    private inline fun <reified T> TestScope.getTelephonyCallbackForType(): T {
+        val cbs = this.getTelephonyCallbacks().filterIsInstance<T>()
         assertThat(cbs.size).isEqualTo(1)
         return cbs[0]
     }
 
     companion object {
-        private val IMMEDIATE = Dispatchers.Main.immediate
-
         // Subscription 1
         private const val SUB_1_ID = 1
         private val GROUP_1 = ParcelUuid(UUID.randomUUID())
@@ -1259,11 +1232,30 @@
         private val SUB_CM =
             mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_CM_ID) }
         private val MODEL_CM = SubscriptionModel(subscriptionId = SUB_CM_ID)
-        private val WIFI_NETWORK_CM =
-            WifiNetworkModel.CarrierMerged(
-                networkId = 3,
-                subscriptionId = SUB_CM_ID,
-                level = 1,
-            )
+
+        private val WIFI_INFO_CM =
+            mock<WifiInfo>().apply {
+                whenever(this.isPrimary).thenReturn(true)
+                whenever(this.isCarrierMerged).thenReturn(true)
+                whenever(this.subscriptionId).thenReturn(SUB_CM_ID)
+            }
+        private val WIFI_NETWORK_CAPS_CM =
+            mock<NetworkCapabilities>().also {
+                whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+                whenever(it.transportInfo).thenReturn(WIFI_INFO_CM)
+                whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(true)
+            }
+
+        private val WIFI_INFO_ACTIVE =
+            mock<WifiInfo>().apply {
+                whenever(this.isPrimary).thenReturn(true)
+                whenever(this.isCarrierMerged).thenReturn(false)
+            }
+        private val WIFI_NETWORK_CAPS_ACTIVE =
+            mock<NetworkCapabilities>().also {
+                whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+                whenever(it.transportInfo).thenReturn(WIFI_INFO_ACTIVE)
+                whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(true)
+            }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index 1c219da..1fb76b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -272,6 +272,52 @@
         }
 
     @Test
+    fun filteredSubscriptions_vcnSubId_agreesWithActiveSubId_usesActiveAkaVcnSub() =
+        testScope.runTest {
+            val (sub1, sub3) =
+                createSubscriptionPair(
+                    subscriptionIds = Pair(SUB_1_ID, SUB_3_ID),
+                    opportunistic = Pair(true, true),
+                    grouped = true,
+                )
+            connectionsRepository.setSubscriptions(listOf(sub1, sub3))
+            connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
+            connectivityRepository.vcnSubId.value = SUB_3_ID
+            whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+                .thenReturn(false)
+
+            var latest: List<SubscriptionModel>? = null
+            val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEqualTo(listOf(sub3))
+
+            job.cancel()
+        }
+
+    @Test
+    fun filteredSubscriptions_vcnSubId_disagreesWithActiveSubId_usesVcnSub() =
+        testScope.runTest {
+            val (sub1, sub3) =
+                createSubscriptionPair(
+                    subscriptionIds = Pair(SUB_1_ID, SUB_3_ID),
+                    opportunistic = Pair(true, true),
+                    grouped = true,
+                )
+            connectionsRepository.setSubscriptions(listOf(sub1, sub3))
+            connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
+            connectivityRepository.vcnSubId.value = SUB_1_ID
+            whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
+                .thenReturn(false)
+
+            var latest: List<SubscriptionModel>? = null
+            val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEqualTo(listOf(sub1))
+
+            job.cancel()
+        }
+
+    @Test
     fun activeDataConnection_turnedOn() =
         testScope.runTest {
             CONNECTION_1.setDataEnabled(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt
index 661002d..fa4e91b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt
@@ -24,6 +24,7 @@
 import android.net.NetworkCapabilities.TRANSPORT_WIFI
 import android.net.vcn.VcnTransportInfo
 import android.net.wifi.WifiInfo
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
@@ -37,6 +38,7 @@
 import com.android.systemui.tuner.TunerService
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -655,6 +657,139 @@
         }
 
     @Test
+    fun vcnSubId_initiallyNull() {
+        assertThat(underTest.vcnSubId.value).isNull()
+    }
+
+    @Test
+    fun vcnSubId_tracksVcnTransportInfo() =
+        testScope.runTest {
+            val vcnInfo = VcnTransportInfo(SUB_1_ID)
+
+            var latest: Int? = null
+            val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this)
+
+            val capabilities =
+                mock<NetworkCapabilities>().also {
+                    whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+                    whenever(it.transportInfo).thenReturn(vcnInfo)
+                }
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
+
+            assertThat(latest).isEqualTo(SUB_1_ID)
+            job.cancel()
+        }
+
+    @Test
+    fun vcnSubId_filersOutInvalid() =
+        testScope.runTest {
+            val vcnInfo = VcnTransportInfo(INVALID_SUBSCRIPTION_ID)
+
+            var latest: Int? = null
+            val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this)
+
+            val capabilities =
+                mock<NetworkCapabilities>().also {
+                    whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+                    whenever(it.transportInfo).thenReturn(vcnInfo)
+                }
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
+
+            assertThat(latest).isNull()
+            job.cancel()
+        }
+
+    @Test
+    fun vcnSubId_nullIfNoTransportInfo() =
+        testScope.runTest {
+            var latest: Int? = null
+            val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this)
+
+            val capabilities =
+                mock<NetworkCapabilities>().also {
+                    whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+                    whenever(it.transportInfo).thenReturn(null)
+                }
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
+
+            assertThat(latest).isNull()
+            job.cancel()
+        }
+
+    @Test
+    fun vcnSubId_nullIfVcnInfoIsNotCellular() =
+        testScope.runTest {
+            // If the underlying network of the VCN is a WiFi network, then there is no subId that
+            // could disagree with telephony's active data subscription id.
+
+            var latest: Int? = null
+            val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this)
+
+            val wifiInfo = mock<WifiInfo>()
+            val vcnInfo = VcnTransportInfo(wifiInfo)
+            val capabilities =
+                mock<NetworkCapabilities>().also {
+                    whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+                    whenever(it.transportInfo).thenReturn(vcnInfo)
+                }
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
+
+            assertThat(latest).isNull()
+            job.cancel()
+        }
+
+    @Test
+    fun vcnSubId_changingVcnInfoIsTracked() =
+        testScope.runTest {
+            var latest: Int? = null
+            val job = underTest.vcnSubId.onEach { latest = it }.launchIn(this)
+
+            val wifiInfo = mock<WifiInfo>()
+            val wifiVcnInfo = VcnTransportInfo(wifiInfo)
+            val sub1VcnInfo = VcnTransportInfo(SUB_1_ID)
+            val sub2VcnInfo = VcnTransportInfo(SUB_2_ID)
+
+            val capabilities =
+                mock<NetworkCapabilities>().also {
+                    whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+                    whenever(it.transportInfo).thenReturn(wifiVcnInfo)
+                }
+
+            // WIFI VCN info
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
+
+            assertThat(latest).isNull()
+
+            // Cellular VCN info with subId 1
+            whenever(capabilities.hasTransport(eq(TRANSPORT_CELLULAR))).thenReturn(true)
+            whenever(capabilities.transportInfo).thenReturn(sub1VcnInfo)
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
+
+            assertThat(latest).isEqualTo(SUB_1_ID)
+
+            // Cellular VCN info with subId 2
+            whenever(capabilities.transportInfo).thenReturn(sub2VcnInfo)
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
+
+            assertThat(latest).isEqualTo(SUB_2_ID)
+
+            // No VCN anymore
+            whenever(capabilities.transportInfo).thenReturn(null)
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, capabilities)
+
+            assertThat(latest).isNull()
+
+            job.cancel()
+        }
+
+    @Test
     fun getMainOrUnderlyingWifiInfo_wifi_hasInfo() {
         val wifiInfo = mock<WifiInfo>()
         val capabilities =
@@ -964,6 +1099,9 @@
         private const val SLOT_WIFI = "wifi"
         private const val SLOT_MOBILE = "mobile"
 
+        private const val SUB_1_ID = 1
+        private const val SUB_2_ID = 2
+
         const val NETWORK_ID = 45
         val NETWORK = mock<Network>().apply { whenever(this.getNetId()).thenReturn(NETWORK_ID) }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
index 9e825b70..8f28cc0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
@@ -30,6 +30,8 @@
     override val defaultConnections: StateFlow<DefaultConnectionModel> =
         MutableStateFlow(DefaultConnectionModel())
 
+    override val vcnSubId: MutableStateFlow<Int?> = MutableStateFlow(null)
+
     fun setForceHiddenIcons(hiddenIcons: Set<ConnectivitySlot>) {
         _forceHiddenIcons.value = hiddenIcons
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
index 5faed9d..c8c24a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -176,4 +177,139 @@
 
             job.cancel()
         }
+
+    @Test
+    fun transitionFromLockscreenToDreamStartedEvent_started_emitted() =
+        testScope.runTest {
+            val emissions by collectValues(underTest.transitionFromLockscreenToDreamStartedEvent)
+
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    KeyguardState.LOCKSCREEN,
+                    KeyguardState.DREAMING,
+                    value = 0f,
+                    TransitionState.STARTED,
+                )
+            )
+
+            assertThat(emissions.size).isEqualTo(1)
+        }
+
+    @Test
+    fun transitionFromLockscreenToDreamStartedEvent_startedMultiple_emittedMultiple() =
+        testScope.runTest {
+            val emissions by collectValues(underTest.transitionFromLockscreenToDreamStartedEvent)
+
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    KeyguardState.LOCKSCREEN,
+                    KeyguardState.DREAMING,
+                    value = 0f,
+                    TransitionState.STARTED,
+                )
+            )
+
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    KeyguardState.LOCKSCREEN,
+                    KeyguardState.DREAMING,
+                    value = 0f,
+                    TransitionState.STARTED,
+                )
+            )
+
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    KeyguardState.LOCKSCREEN,
+                    KeyguardState.DREAMING,
+                    value = 0f,
+                    TransitionState.STARTED,
+                )
+            )
+
+            assertThat(emissions.size).isEqualTo(3)
+        }
+
+    @Test
+    fun transitionFromLockscreenToDreamStartedEvent_startedThenRunning_emittedOnlyOne() =
+        testScope.runTest {
+            val emissions by collectValues(underTest.transitionFromLockscreenToDreamStartedEvent)
+
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    KeyguardState.LOCKSCREEN,
+                    KeyguardState.DREAMING,
+                    value = 0f,
+                    TransitionState.STARTED,
+                )
+            )
+            assertThat(emissions.size).isEqualTo(1)
+
+            // WHEN the transition progresses through its animation by going through the RUNNING
+            // step with increasing fractions
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    KeyguardState.LOCKSCREEN,
+                    KeyguardState.DREAMING,
+                    value = .1f,
+                    TransitionState.RUNNING,
+                )
+            )
+
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    KeyguardState.LOCKSCREEN,
+                    KeyguardState.DREAMING,
+                    value = .2f,
+                    TransitionState.RUNNING,
+                )
+            )
+
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    KeyguardState.LOCKSCREEN,
+                    KeyguardState.DREAMING,
+                    value = .3f,
+                    TransitionState.RUNNING,
+                )
+            )
+
+            // THEN the flow does not emit since the flow should only emit when the transition
+            // starts
+            assertThat(emissions.size).isEqualTo(1)
+        }
+
+    @Test
+    fun transitionFromLockscreenToDreamStartedEvent_irrelevantTransition_notEmitted() =
+        testScope.runTest {
+            val emissions by collectValues(underTest.transitionFromLockscreenToDreamStartedEvent)
+
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    KeyguardState.LOCKSCREEN,
+                    KeyguardState.OCCLUDED,
+                    value = 0f,
+                    TransitionState.STARTED,
+                )
+            )
+
+            assertThat(emissions).isEmpty()
+        }
+
+    @Test
+    fun transitionFromLockscreenToDreamStartedEvent_irrelevantTransitionState_notEmitted() =
+        testScope.runTest {
+            val emissions by collectValues(underTest.transitionFromLockscreenToDreamStartedEvent)
+
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    KeyguardState.LOCKSCREEN,
+                    KeyguardState.DREAMING,
+                    value = 1.0f,
+                    TransitionState.FINISHED,
+                )
+            )
+
+            assertThat(emissions).isEmpty()
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewBinder.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewBinder.kt
new file mode 100644
index 0000000..2ee928f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewBinder.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.pipeline.shared.ui.viewmodel
+
+import android.view.View
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusBarViewBinder
+import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarVisibilityChangeListener
+
+/**
+ * A fake view binder that can be used from Java tests.
+ *
+ * Since Java tests can't run tests within test scopes, we need to bypass the flows from
+ * [CollapsedStatusBarViewModel] and just trigger the listener directly.
+ */
+class FakeCollapsedStatusBarViewBinder : CollapsedStatusBarViewBinder {
+    var listener: StatusBarVisibilityChangeListener? = null
+
+    override fun bind(
+        view: View,
+        viewModel: CollapsedStatusBarViewModel,
+        listener: StatusBarVisibilityChangeListener,
+    ) {
+        this.listener = listener
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt
index cbf6637..88587b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeCollapsedStatusBarViewModel.kt
@@ -16,8 +16,11 @@
 
 package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel
 
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 
 class FakeCollapsedStatusBarViewModel : CollapsedStatusBarViewModel {
     override val isTransitioningFromLockscreenToOccluded = MutableStateFlow(false)
+
+    override val transitionFromLockscreenToDreamStartedEvent = MutableSharedFlow<Unit>()
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
index 2b13705..7402b4d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
@@ -14,6 +14,8 @@
 
 package com.android.systemui.statusbar.policy;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -44,7 +46,11 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.bluetooth.BluetoothLogger;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepository;
+import com.android.systemui.statusbar.policy.bluetooth.FakeBluetoothRepository;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
@@ -69,6 +75,7 @@
     private DumpManager mMockDumpManager;
     private BluetoothControllerImpl mBluetoothControllerImpl;
     private BluetoothAdapter mMockAdapter;
+    private final FakeFeatureFlags mFakeFeatureFlags = new FakeFeatureFlags();
 
     private List<CachedBluetoothDevice> mDevices;
 
@@ -89,17 +96,26 @@
                 .thenReturn(mock(LocalBluetoothProfileManager.class));
         mMockDumpManager = mock(DumpManager.class);
 
-        mBluetoothControllerImpl = new BluetoothControllerImpl(mContext,
+        BluetoothRepository bluetoothRepository =
+                new FakeBluetoothRepository(mMockBluetoothManager);
+        mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, true);
+
+        mBluetoothControllerImpl = new BluetoothControllerImpl(
+                mContext,
+                mFakeFeatureFlags,
                 mUserTracker,
                 mMockDumpManager,
                 mock(BluetoothLogger.class),
+                bluetoothRepository,
                 mTestableLooper.getLooper(),
                 mMockBluetoothManager,
                 mMockAdapter);
     }
 
     @Test
-    public void testNoConnectionWithDevices() {
+    public void testNoConnectionWithDevices_repoFlagOff() {
+        mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, false);
+
         CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
         when(device.isConnected()).thenReturn(true);
         when(device.getMaxConnectionState()).thenReturn(BluetoothProfile.STATE_CONNECTED);
@@ -113,7 +129,27 @@
     }
 
     @Test
-    public void testOnServiceConnected_updatesConnectionState() {
+    public void testNoConnectionWithDevices_repoFlagOn() {
+        mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, true);
+
+        CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
+        when(device.isConnected()).thenReturn(true);
+        when(device.getMaxConnectionState()).thenReturn(BluetoothProfile.STATE_CONNECTED);
+
+        mDevices.add(device);
+        when(mMockLocalAdapter.getConnectionState())
+                .thenReturn(BluetoothAdapter.STATE_DISCONNECTED);
+
+        mBluetoothControllerImpl.onConnectionStateChanged(null,
+                BluetoothAdapter.STATE_DISCONNECTED);
+
+        assertTrue(mBluetoothControllerImpl.isBluetoothConnected());
+    }
+
+    @Test
+    public void testOnServiceConnected_updatesConnectionState_repoFlagOff() {
+        mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, false);
+
         when(mMockLocalAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_CONNECTING);
 
         mBluetoothControllerImpl.onServiceConnected();
@@ -123,6 +159,58 @@
     }
 
     @Test
+    public void testOnServiceConnected_updatesConnectionState_repoFlagOn() {
+        mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, true);
+
+        when(mMockLocalAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_CONNECTING);
+
+        mBluetoothControllerImpl.onServiceConnected();
+
+        assertTrue(mBluetoothControllerImpl.isBluetoothConnecting());
+        assertFalse(mBluetoothControllerImpl.isBluetoothConnected());
+    }
+
+    @Test
+    public void getConnectedDevices_onlyReturnsConnected_repoFlagOff() {
+        mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, false);
+
+        CachedBluetoothDevice device1Disconnected = mock(CachedBluetoothDevice.class);
+        when(device1Disconnected.isConnected()).thenReturn(false);
+        mDevices.add(device1Disconnected);
+
+        CachedBluetoothDevice device2Connected = mock(CachedBluetoothDevice.class);
+        when(device2Connected.isConnected()).thenReturn(true);
+        mDevices.add(device2Connected);
+
+        mBluetoothControllerImpl.onDeviceAdded(device1Disconnected);
+        mBluetoothControllerImpl.onDeviceAdded(device2Connected);
+
+        assertThat(mBluetoothControllerImpl.getConnectedDevices()).hasSize(1);
+        assertThat(mBluetoothControllerImpl.getConnectedDevices().get(0))
+                .isEqualTo(device2Connected);
+    }
+
+    @Test
+    public void getConnectedDevices_onlyReturnsConnected_repoFlagOn() {
+        mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, true);
+
+        CachedBluetoothDevice device1Disconnected = mock(CachedBluetoothDevice.class);
+        when(device1Disconnected.isConnected()).thenReturn(false);
+        mDevices.add(device1Disconnected);
+
+        CachedBluetoothDevice device2Connected = mock(CachedBluetoothDevice.class);
+        when(device2Connected.isConnected()).thenReturn(true);
+        mDevices.add(device2Connected);
+
+        mBluetoothControllerImpl.onDeviceAdded(device1Disconnected);
+        mBluetoothControllerImpl.onDeviceAdded(device2Connected);
+
+        assertThat(mBluetoothControllerImpl.getConnectedDevices()).hasSize(1);
+        assertThat(mBluetoothControllerImpl.getConnectedDevices().get(0))
+                .isEqualTo(device2Connected);
+    }
+
+    @Test
     public void testOnBluetoothStateChange_updatesBluetoothState() {
         mBluetoothControllerImpl.onBluetoothStateChanged(BluetoothAdapter.STATE_OFF);
 
@@ -147,8 +235,9 @@
     }
 
     @Test
-    public void testOnACLConnectionStateChange_updatesBluetoothStateOnConnection()
-            throws Exception {
+    public void testOnACLConnectionStateChange_updatesBluetoothStateOnConnection_repoFlagOff() {
+        mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, false);
+
         BluetoothController.Callback callback = mock(BluetoothController.Callback.class);
         mBluetoothControllerImpl.addCallback(callback);
 
@@ -168,6 +257,29 @@
     }
 
     @Test
+    public void testOnACLConnectionStateChange_updatesBluetoothStateOnConnection_repoFlagOn() {
+        mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, true);
+
+        BluetoothController.Callback callback = mock(BluetoothController.Callback.class);
+        mBluetoothControllerImpl.addCallback(callback);
+
+        assertFalse(mBluetoothControllerImpl.isBluetoothConnected());
+        CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
+        mDevices.add(device);
+        when(device.isConnected()).thenReturn(true);
+        when(device.getMaxConnectionState()).thenReturn(BluetoothProfile.STATE_CONNECTED);
+        reset(callback);
+        mBluetoothControllerImpl.onAclConnectionStateChanged(device,
+                BluetoothProfile.STATE_CONNECTED);
+
+        mTestableLooper.processAllMessages();
+
+        assertTrue(mBluetoothControllerImpl.isBluetoothConnected());
+        verify(callback, atLeastOnce()).onBluetoothStateChange(anyBoolean());
+    }
+
+
+    @Test
     public void testOnActiveDeviceChanged_updatesAudioActive() {
         assertFalse(mBluetoothControllerImpl.isBluetoothAudioActive());
         assertFalse(mBluetoothControllerImpl.isBluetoothAudioProfileOnly());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepositoryImplTest.kt
new file mode 100644
index 0000000..6f40f15
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/bluetooth/BluetoothRepositoryImplTest.kt
@@ -0,0 +1,213 @@
+/*
+ * 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.policy.bluetooth
+
+import android.bluetooth.BluetoothProfile
+import androidx.test.filters.SmallTest
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.LocalBluetoothAdapter
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.TestScope
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class BluetoothRepositoryImplTest : SysuiTestCase() {
+
+    private lateinit var underTest: BluetoothRepositoryImpl
+
+    private lateinit var scheduler: TestCoroutineScheduler
+    private lateinit var dispatcher: TestDispatcher
+    private lateinit var testScope: TestScope
+
+    @Mock private lateinit var localBluetoothManager: LocalBluetoothManager
+    @Mock private lateinit var bluetoothAdapter: LocalBluetoothAdapter
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(localBluetoothManager.bluetoothAdapter).thenReturn(bluetoothAdapter)
+
+        scheduler = TestCoroutineScheduler()
+        dispatcher = StandardTestDispatcher(scheduler)
+        testScope = TestScope(dispatcher)
+
+        underTest =
+            BluetoothRepositoryImpl(testScope.backgroundScope, dispatcher, localBluetoothManager)
+    }
+
+    @Test
+    fun fetchConnectionStatusInBackground_currentDevicesEmpty_maxStateIsManagerState() {
+        whenever(bluetoothAdapter.connectionState).thenReturn(BluetoothProfile.STATE_CONNECTING)
+
+        val status = fetchConnectionStatus(currentDevices = emptyList())
+
+        assertThat(status.maxConnectionState).isEqualTo(BluetoothProfile.STATE_CONNECTING)
+    }
+
+    @Test
+    fun fetchConnectionStatusInBackground_currentDevicesEmpty_nullManager_maxStateIsDisconnected() {
+        // This CONNECTING state should be unused because localBluetoothManager is null
+        whenever(bluetoothAdapter.connectionState).thenReturn(BluetoothProfile.STATE_CONNECTING)
+        underTest =
+            BluetoothRepositoryImpl(
+                testScope.backgroundScope,
+                dispatcher,
+                localBluetoothManager = null,
+            )
+
+        val status = fetchConnectionStatus(currentDevices = emptyList())
+
+        assertThat(status.maxConnectionState).isEqualTo(BluetoothProfile.STATE_DISCONNECTED)
+    }
+
+    @Test
+    fun fetchConnectionStatusInBackground_managerStateLargerThanDeviceStates_maxStateIsManager() {
+        whenever(bluetoothAdapter.connectionState).thenReturn(BluetoothProfile.STATE_CONNECTING)
+        val device1 =
+            mock<CachedBluetoothDevice>().also {
+                whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_DISCONNECTED)
+            }
+        val device2 =
+            mock<CachedBluetoothDevice>().also {
+                whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_DISCONNECTED)
+            }
+
+        val status = fetchConnectionStatus(currentDevices = listOf(device1, device2))
+
+        assertThat(status.maxConnectionState).isEqualTo(BluetoothProfile.STATE_CONNECTING)
+    }
+
+    @Test
+    fun fetchConnectionStatusInBackground_oneCurrentDevice_maxStateIsDeviceState() {
+        whenever(bluetoothAdapter.connectionState).thenReturn(BluetoothProfile.STATE_DISCONNECTED)
+        val device =
+            mock<CachedBluetoothDevice>().also {
+                whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_CONNECTING)
+            }
+
+        val status = fetchConnectionStatus(currentDevices = listOf(device))
+
+        assertThat(status.maxConnectionState).isEqualTo(BluetoothProfile.STATE_CONNECTING)
+    }
+
+    @Test
+    fun fetchConnectionStatusInBackground_multipleDevices_maxStateIsHighestState() {
+        whenever(bluetoothAdapter.connectionState).thenReturn(BluetoothProfile.STATE_DISCONNECTED)
+
+        val device1 =
+            mock<CachedBluetoothDevice>().also {
+                whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_CONNECTING)
+                whenever(it.isConnected).thenReturn(false)
+            }
+        val device2 =
+            mock<CachedBluetoothDevice>().also {
+                whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_CONNECTED)
+                whenever(it.isConnected).thenReturn(true)
+            }
+
+        val status = fetchConnectionStatus(currentDevices = listOf(device1, device2))
+
+        assertThat(status.maxConnectionState).isEqualTo(BluetoothProfile.STATE_CONNECTED)
+    }
+
+    @Test
+    fun fetchConnectionStatusInBackground_devicesNotConnected_maxStateIsDisconnected() {
+        whenever(bluetoothAdapter.connectionState).thenReturn(BluetoothProfile.STATE_CONNECTING)
+
+        // WHEN the devices say their state is CONNECTED but [isConnected] is false
+        val device1 =
+            mock<CachedBluetoothDevice>().also {
+                whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_CONNECTED)
+                whenever(it.isConnected).thenReturn(false)
+            }
+        val device2 =
+            mock<CachedBluetoothDevice>().also {
+                whenever(it.maxConnectionState).thenReturn(BluetoothProfile.STATE_CONNECTED)
+                whenever(it.isConnected).thenReturn(false)
+            }
+
+        val status = fetchConnectionStatus(currentDevices = listOf(device1, device2))
+
+        // THEN the max state is DISCONNECTED
+        assertThat(status.maxConnectionState).isEqualTo(BluetoothProfile.STATE_DISCONNECTED)
+    }
+
+    @Test
+    fun fetchConnectionStatusInBackground_currentDevicesEmpty_connectedDevicesEmpty() {
+        val status = fetchConnectionStatus(currentDevices = emptyList())
+
+        assertThat(status.connectedDevices).isEmpty()
+    }
+
+    @Test
+    fun fetchConnectionStatusInBackground_oneCurrentDeviceDisconnected_connectedDevicesEmpty() {
+        val device =
+            mock<CachedBluetoothDevice>().also { whenever(it.isConnected).thenReturn(false) }
+
+        val status = fetchConnectionStatus(currentDevices = listOf(device))
+
+        assertThat(status.connectedDevices).isEmpty()
+    }
+
+    @Test
+    fun fetchConnectionStatusInBackground_oneCurrentDeviceConnected_connectedDevicesHasDevice() {
+        val device =
+            mock<CachedBluetoothDevice>().also { whenever(it.isConnected).thenReturn(true) }
+
+        val status = fetchConnectionStatus(currentDevices = listOf(device))
+
+        assertThat(status.connectedDevices).isEqualTo(listOf(device))
+    }
+
+    @Test
+    fun fetchConnectionStatusInBackground_multipleDevices_connectedDevicesHasOnlyConnected() {
+        val device1Connected =
+            mock<CachedBluetoothDevice>().also { whenever(it.isConnected).thenReturn(true) }
+        val device2Disconnected =
+            mock<CachedBluetoothDevice>().also { whenever(it.isConnected).thenReturn(false) }
+        val device3Connected =
+            mock<CachedBluetoothDevice>().also { whenever(it.isConnected).thenReturn(true) }
+
+        val status =
+            fetchConnectionStatus(
+                currentDevices = listOf(device1Connected, device2Disconnected, device3Connected)
+            )
+
+        assertThat(status.connectedDevices).isEqualTo(listOf(device1Connected, device3Connected))
+    }
+
+    private fun fetchConnectionStatus(
+        currentDevices: Collection<CachedBluetoothDevice>
+    ): ConnectionStatusModel {
+        var receivedStatus: ConnectionStatusModel? = null
+        underTest.fetchConnectionStatusInBackground(currentDevices) { status ->
+            receivedStatus = status
+        }
+        scheduler.runCurrent()
+        return receivedStatus!!
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/bluetooth/FakeBluetoothRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/bluetooth/FakeBluetoothRepository.kt
new file mode 100644
index 0000000..d8c0f77
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/bluetooth/FakeBluetoothRepository.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy.bluetooth
+
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
+
+/**
+ * Fake [BluetoothRepository] that delegates to the real [BluetoothRepositoryImpl].
+ *
+ * We only need this because [BluetoothRepository] is called from Java, which can't use [TestScope],
+ * [StandardTestDispatcher], etc. to create a test version of the repo. This class uses those test
+ * items under-the-hood so Java classes can indirectly access them.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+class FakeBluetoothRepository(localBluetoothManager: LocalBluetoothManager) : BluetoothRepository {
+
+    private val scheduler = TestCoroutineScheduler()
+    private val dispatcher = StandardTestDispatcher(scheduler)
+    private val testScope = TestScope(dispatcher)
+
+    private val impl =
+        BluetoothRepositoryImpl(testScope.backgroundScope, dispatcher, localBluetoothManager)
+
+    override fun fetchConnectionStatusInBackground(
+        currentDevices: Collection<CachedBluetoothDevice>,
+        callback: ConnectionStatusFetchedCallback
+    ) {
+        impl.fetchConnectionStatusInBackground(currentDevices, callback)
+        scheduler.runCurrent()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index 17b5e05..09ac0e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.theme;
 
+import static android.util.TypedValue.TYPE_INT_COLOR_ARGB8;
+
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_ACCENT_COLOR;
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_CATEGORY_SYSTEM_PALETTE;
@@ -29,6 +31,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
@@ -926,4 +929,38 @@
         verify(mThemeOverlayApplier, never()).applyCurrentUserOverlays(any(), any(), anyInt(),
                 any());
     }
+
+    @Test
+    public void createDynamicOverlay_addsAllDynamicColors() {
+        // Trigger new wallpaper colors to generate an overlay
+        WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
+                Color.valueOf(Color.BLUE), null);
+        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
+                USER_SYSTEM);
+        ArgumentCaptor<FabricatedOverlay[]> themeOverlays =
+                ArgumentCaptor.forClass(FabricatedOverlay[].class);
+
+        verify(mThemeOverlayApplier)
+                .applyCurrentUserOverlays(any(), themeOverlays.capture(), anyInt(), any());
+
+        FabricatedOverlay[] overlays = themeOverlays.getValue();
+        FabricatedOverlay accents = overlays[0];
+        FabricatedOverlay neutrals = overlays[1];
+        FabricatedOverlay dynamic = overlays[2];
+
+        final int colorsPerPalette = 12;
+
+        // Color resources were added for all 3 accent palettes
+        verify(accents, times(colorsPerPalette * 3))
+                .setResourceValue(any(String.class), eq(TYPE_INT_COLOR_ARGB8), anyInt(), eq(null));
+        // Color resources were added for all 2 neutral palettes
+        verify(neutrals, times(colorsPerPalette * 2))
+                .setResourceValue(any(String.class), eq(TYPE_INT_COLOR_ARGB8), anyInt(), eq(null));
+        // All dynamic colors were added twice: light and dark them
+        // All fixed colors were added once
+        verify(dynamic, times(
+                DynamicColors.ALL_DYNAMIC_COLORS_MAPPED.size() * 2
+                        + DynamicColors.FIXED_COLORS_MAPPED.size())
+        ).setResourceValue(any(String.class), eq(TYPE_INT_COLOR_ARGB8), anyInt(), eq(null));
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
index 6298506..d9ee081 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt
@@ -146,7 +146,7 @@
     fun onFolded_aodDisabled_doesNotLogLatency() =
         runBlocking(IMMEDIATE) {
             val job = underTest.listenForDozing(this)
-            keyguardRepository.setDozing(true)
+            keyguardRepository.setIsDozing(true)
             setAodEnabled(enabled = false)
 
             yield()
@@ -163,7 +163,7 @@
     fun onFolded_aodEnabled_logsLatency() =
         runBlocking(IMMEDIATE) {
             val job = underTest.listenForDozing(this)
-            keyguardRepository.setDozing(true)
+            keyguardRepository.setIsDozing(true)
             setAodEnabled(enabled = true)
 
             yield()
@@ -181,7 +181,7 @@
     fun onFolded_onScreenTurningOnInvokedTwice_doesNotLogLatency() =
         runBlocking(IMMEDIATE) {
             val job = underTest.listenForDozing(this)
-            keyguardRepository.setDozing(true)
+            keyguardRepository.setIsDozing(true)
             setAodEnabled(enabled = true)
 
             yield()
@@ -203,7 +203,7 @@
     fun onFolded_onScreenTurningOnWithoutDozingThenWithDozing_doesNotLogLatency() =
         runBlocking(IMMEDIATE) {
             val job = underTest.listenForDozing(this)
-            keyguardRepository.setDozing(false)
+            keyguardRepository.setIsDozing(false)
             setAodEnabled(enabled = true)
 
             yield()
@@ -214,7 +214,7 @@
 
             // Now enable dozing and trigger a second run through the aod animation code. It should
             // not rerun the animation
-            keyguardRepository.setDozing(true)
+            keyguardRepository.setIsDozing(true)
             yield()
             simulateScreenTurningOn()
 
@@ -228,7 +228,7 @@
     fun onFolded_animationCancelled_doesNotLogLatency() =
         runBlocking(IMMEDIATE) {
             val job = underTest.listenForDozing(this)
-            keyguardRepository.setDozing(true)
+            keyguardRepository.setIsDozing(true)
             setAodEnabled(enabled = true)
 
             yield()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index e06b43a..45a37cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -17,22 +17,28 @@
 package com.android.systemui.volume;
 
 import static com.android.systemui.volume.Events.DISMISS_REASON_UNKNOWN;
+import static com.android.systemui.volume.Events.SHOW_REASON_UNKNOWN;
 import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.KeyguardManager;
+import android.content.res.Configuration;
 import android.media.AudioManager;
 import android.os.SystemClock;
 import android.provider.DeviceConfig;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.view.Gravity;
 import android.view.InputDevice;
 import android.view.MotionEvent;
 import android.view.View;
@@ -53,7 +59,9 @@
 import com.android.systemui.plugins.VolumeDialogController.State;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.FakeConfigurationController;
 import com.android.systemui.util.DeviceConfigProxyFake;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
@@ -82,6 +90,9 @@
     View mDrawerNormal;
     private DeviceConfigProxyFake mDeviceConfigProxy;
     private FakeExecutor mExecutor;
+    private TestableLooper mTestableLooper;
+    private ConfigurationController mConfigurationController;
+    private int mOriginalOrientation;
 
     @Mock
     VolumeDialogController mVolumeDialogController;
@@ -92,8 +103,6 @@
     @Mock
     DeviceProvisionedController mDeviceProvisionedController;
     @Mock
-    ConfigurationController mConfigurationController;
-    @Mock
     MediaOutputDialogFactory mMediaOutputDialogFactory;
     @Mock
     VolumePanelFactory mVolumePanelFactory;
@@ -104,6 +113,8 @@
     @Mock
     private DumpManager mDumpManager;
     @Mock CsdWarningDialog mCsdWarningDialog;
+    @Mock
+    DevicePostureController mPostureController;
 
     private final CsdWarningDialog.Factory mCsdWarningDialogFactory =
             new CsdWarningDialog.Factory() {
@@ -119,9 +130,17 @@
 
         getContext().addMockSystemService(KeyguardManager.class, mKeyguard);
 
+        mTestableLooper = TestableLooper.get(this);
         mDeviceConfigProxy = new DeviceConfigProxyFake();
         mExecutor = new FakeExecutor(new FakeSystemClock());
 
+        when(mPostureController.getDevicePosture())
+                .thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED);
+
+        mOriginalOrientation = mContext.getResources().getConfiguration().orientation;
+
+        mConfigurationController = new FakeConfigurationController();
+
         mDialog = new VolumeDialogImpl(
                 getContext(),
                 mVolumeDialogController,
@@ -135,8 +154,9 @@
                 mDeviceConfigProxy,
                 mExecutor,
                 mCsdWarningDialogFactory,
-                mDumpManager
-            );
+                mPostureController,
+                mTestableLooper.getLooper(),
+                mDumpManager);
         mDialog.init(0, null);
         State state = createShellState();
         mDialog.onStateChangedH(state);
@@ -227,6 +247,7 @@
                 ArgumentCaptor.forClass(VolumeDialogController.Callbacks.class);
         verify(mVolumeDialogController).addCallback(controllerCallbackCapture.capture(), any());
         VolumeDialogController.Callbacks callbacks = controllerCallbackCapture.getValue();
+
         callbacks.onShowSafetyWarning(AudioManager.FLAG_SHOW_UI);
         verify(mAccessibilityMgr).getRecommendedTimeoutMillis(
                 VolumeDialogImpl.DIALOG_SAFETYWARNING_TIMEOUT_MILLIS,
@@ -371,11 +392,171 @@
         verify(mCsdWarningDialog).show();
     }
 
+    @Test
+    public void ifPortraitHalfOpen_drawVerticallyTop() {
+        DevicePostureController devicePostureController = mock(DevicePostureController.class);
+        when(devicePostureController.getDevicePosture())
+                .thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED);
+
+        VolumeDialogImpl dialog = new VolumeDialogImpl(
+                getContext(),
+                mVolumeDialogController,
+                mAccessibilityMgr,
+                mDeviceProvisionedController,
+                mConfigurationController,
+                mMediaOutputDialogFactory,
+                mVolumePanelFactory,
+                mActivityStarter,
+                mInteractionJankMonitor,
+                mDeviceConfigProxy,
+                mExecutor,
+                mCsdWarningDialogFactory,
+                devicePostureController,
+                mTestableLooper.getLooper(),
+                mDumpManager
+        );
+        dialog.init(0 , null);
+
+        verify(devicePostureController).addCallback(any());
+        dialog.onPostureChanged(DevicePostureController.DEVICE_POSTURE_HALF_OPENED);
+        mTestableLooper.processAllMessages(); // let dismiss() finish
+
+        setOrientation(Configuration.ORIENTATION_PORTRAIT);
+
+        // Call show() to trigger layout updates before verifying position
+        dialog.show(SHOW_REASON_UNKNOWN);
+        mTestableLooper.processAllMessages(); // let show() finish before assessing its side-effect
+
+        int gravity = dialog.getWindowGravity();
+        assertEquals(Gravity.TOP, gravity & Gravity.VERTICAL_GRAVITY_MASK);
+    }
+
+    @Test
+    public void ifPortraitAndOpen_drawCenterVertically() {
+        DevicePostureController devicePostureController = mock(DevicePostureController.class);
+        when(devicePostureController.getDevicePosture())
+                .thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED);
+
+        VolumeDialogImpl dialog = new VolumeDialogImpl(
+                getContext(),
+                mVolumeDialogController,
+                mAccessibilityMgr,
+                mDeviceProvisionedController,
+                mConfigurationController,
+                mMediaOutputDialogFactory,
+                mVolumePanelFactory,
+                mActivityStarter,
+                mInteractionJankMonitor,
+                mDeviceConfigProxy,
+                mExecutor,
+                mCsdWarningDialogFactory,
+                devicePostureController,
+                mTestableLooper.getLooper(),
+                mDumpManager
+        );
+        dialog.init(0, null);
+
+        verify(devicePostureController).addCallback(any());
+        dialog.onPostureChanged(DevicePostureController.DEVICE_POSTURE_OPENED);
+        mTestableLooper.processAllMessages(); // let dismiss() finish
+
+        setOrientation(Configuration.ORIENTATION_PORTRAIT);
+
+        dialog.show(SHOW_REASON_UNKNOWN);
+        mTestableLooper.processAllMessages(); // let show() finish before assessing its side-effect
+
+        int gravity = dialog.getWindowGravity();
+        assertEquals(Gravity.CENTER_VERTICAL, gravity & Gravity.VERTICAL_GRAVITY_MASK);
+    }
+
+    @Test
+    public void ifLandscapeAndHalfOpen_drawCenterVertically() {
+        DevicePostureController devicePostureController = mock(DevicePostureController.class);
+        when(devicePostureController.getDevicePosture())
+                .thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED);
+
+        VolumeDialogImpl dialog = new VolumeDialogImpl(
+                getContext(),
+                mVolumeDialogController,
+                mAccessibilityMgr,
+                mDeviceProvisionedController,
+                mConfigurationController,
+                mMediaOutputDialogFactory,
+                mVolumePanelFactory,
+                mActivityStarter,
+                mInteractionJankMonitor,
+                mDeviceConfigProxy,
+                mExecutor,
+                mCsdWarningDialogFactory,
+                devicePostureController,
+                mTestableLooper.getLooper(),
+                mDumpManager
+        );
+        dialog.init(0, null);
+
+        verify(devicePostureController).addCallback(any());
+        dialog.onPostureChanged(DevicePostureController.DEVICE_POSTURE_HALF_OPENED);
+        mTestableLooper.processAllMessages(); // let dismiss() finish
+
+        setOrientation(Configuration.ORIENTATION_LANDSCAPE);
+
+        dialog.show(SHOW_REASON_UNKNOWN);
+        mTestableLooper.processAllMessages(); // let show() finish before assessing its side-effect
+
+        int gravity = dialog.getWindowGravity();
+        assertEquals(Gravity.CENTER_VERTICAL, gravity & Gravity.VERTICAL_GRAVITY_MASK);
+    }
+
+    @Test
+    public void dialogInit_addsPostureControllerCallback() {
+        // init is already called in setup
+        verify(mPostureController).addCallback(any());
+    }
+
+    @Test
+    public void dialogDestroy_removesPostureControllerCallback() {
+        VolumeDialogImpl dialog = new VolumeDialogImpl(
+                getContext(),
+                mVolumeDialogController,
+                mAccessibilityMgr,
+                mDeviceProvisionedController,
+                mConfigurationController,
+                mMediaOutputDialogFactory,
+                mVolumePanelFactory,
+                mActivityStarter,
+                mInteractionJankMonitor,
+                mDeviceConfigProxy,
+                mExecutor,
+                mCsdWarningDialogFactory,
+                mPostureController,
+                mTestableLooper.getLooper(),
+                mDumpManager
+        );
+        dialog.init(0, null);
+
+        verify(mPostureController, never()).removeCallback(any());
+
+        dialog.destroy();
+
+        verify(mPostureController).removeCallback(any());
+    }
+
+    private void setOrientation(int orientation) {
+        Configuration config = new Configuration();
+        config.orientation = orientation;
+        if (mConfigurationController != null) {
+            mConfigurationController.onConfigurationChanged(config);
+        }
+    }
+
     @After
     public void teardown() {
         if (mDialog != null) {
             mDialog.clearInternalHandlerAfterTest();
         }
+        setOrientation(mOriginalOrientation);
+        mTestableLooper.processAllMessages();
+        reset(mPostureController);
     }
 
 /*
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index 1ec4e8c..8bbd58d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -102,7 +102,8 @@
                 mock(Executor.class),
                 mock(DumpManager.class),
                 mock(BroadcastDispatcherLogger.class),
-                mock(UserTracker.class));
+                mock(UserTracker.class),
+                shouldFailOnLeakedReceiver());
 
         mRealInstrumentation = InstrumentationRegistry.getInstrumentation();
         Instrumentation inst = spy(mRealInstrumentation);
@@ -141,6 +142,10 @@
         mDependency.injectTestDependency(DialogLaunchAnimator.class, fakeDialogLaunchAnimator());
     }
 
+    protected boolean shouldFailOnLeakedReceiver() {
+        return false;
+    }
+
     @After
     public void SysuiTeardown() {
         if (mRealInstrumentation != null) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
index d9012a5..2362a52 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
@@ -39,20 +39,21 @@
         MutableStateFlow(FingerprintSensorType.UNKNOWN)
     override val sensorType: StateFlow<FingerprintSensorType> = _sensorType.asStateFlow()
 
-    private val _sensorLocation: MutableStateFlow<SensorLocationInternal> =
-        MutableStateFlow(SensorLocationInternal.DEFAULT)
-    override val sensorLocation = _sensorLocation.asStateFlow()
+    private val _sensorLocations: MutableStateFlow<Map<String, SensorLocationInternal>> =
+        MutableStateFlow(mapOf("" to SensorLocationInternal.DEFAULT))
+    override val sensorLocations: StateFlow<Map<String, SensorLocationInternal>> =
+        _sensorLocations.asStateFlow()
 
     fun setProperties(
         sensorId: Int,
         strength: SensorStrength,
         sensorType: FingerprintSensorType,
-        sensorLocation: SensorLocationInternal
+        sensorLocations: Map<String, SensorLocationInternal>
     ) {
         _sensorId.value = sensorId
         _strength.value = strength
         _sensorType.value = sensorType
-        _sensorLocation.value = sensorLocation
+        _sensorLocations.value = sensorLocations
         _isInitialized.value = true
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
index ad086ff..af940e4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/broadcast/FakeBroadcastDispatcher.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.settings.UserTracker
+import java.lang.IllegalStateException
 import java.util.concurrent.ConcurrentHashMap
 import java.util.concurrent.Executor
 
@@ -37,7 +38,8 @@
     broadcastRunningExecutor: Executor,
     dumpManager: DumpManager,
     logger: BroadcastDispatcherLogger,
-    userTracker: UserTracker
+    userTracker: UserTracker,
+    private val shouldFailOnLeakedReceiver: Boolean
 ) :
     BroadcastDispatcher(
         context,
@@ -85,6 +87,9 @@
     fun cleanUpReceivers(testName: String) {
         registeredReceivers.forEach {
             Log.i(testName, "Receiver not unregistered from dispatcher: $it")
+            if (shouldFailOnLeakedReceiver) {
+                throw IllegalStateException("Receiver not unregistered from dispatcher: $it")
+            }
         }
         registeredReceivers.clear()
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index d411590..fd8c4b8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -54,7 +54,10 @@
     override val isKeyguardOccluded: Flow<Boolean> = _isKeyguardOccluded
 
     private val _isDozing = MutableStateFlow(false)
-    override val isDozing: Flow<Boolean> = _isDozing
+    override val isDozing: StateFlow<Boolean> = _isDozing
+
+    private val _lastDozeTapToWakePosition = MutableStateFlow<Point?>(null)
+    override val lastDozeTapToWakePosition = _lastDozeTapToWakePosition.asStateFlow()
 
     private val _isAodAvailable = MutableStateFlow(false)
     override val isAodAvailable: Flow<Boolean> = _isAodAvailable
@@ -76,12 +79,7 @@
 
     private val _wakefulnessModel =
         MutableStateFlow(
-            WakefulnessModel(
-                WakefulnessState.ASLEEP,
-                false,
-                WakeSleepReason.OTHER,
-                WakeSleepReason.OTHER
-            )
+            WakefulnessModel(WakefulnessState.ASLEEP, WakeSleepReason.OTHER, WakeSleepReason.OTHER)
         )
     override val wakefulness: Flow<WakefulnessModel> = _wakefulnessModel
 
@@ -137,10 +135,14 @@
         _isKeyguardOccluded.value = isOccluded
     }
 
-    fun setDozing(isDozing: Boolean) {
+    override fun setIsDozing(isDozing: Boolean) {
         _isDozing.value = isDozing
     }
 
+    override fun setLastDozeTapToWakePosition(position: Point) {
+        _lastDozeTapToWakePosition.value = position
+    }
+
     fun setAodAvailable(isAodAvailable: Boolean) {
         _isAodAvailable.value = isAodAvailable
     }
diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
index 6aca2fd..496f4f6 100644
--- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
+++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
@@ -365,6 +365,11 @@
         final int sysWhich = FLAG_SYSTEM | (lockImageStage.exists() ? 0 : FLAG_LOCK);
 
         try {
+            // First parse the live component name so that we know for logging if we care about
+            // logging errors with the image restore.
+            ComponentName wpService = parseWallpaperComponent(infoStage, "wp");
+            mSystemHasLiveComponent = wpService != null;
+
             // It is valid for the imagery to be absent; it means that we were not permitted
             // to back up the original image on the source device, or there was no user-supplied
             // wallpaper image present.
@@ -372,10 +377,10 @@
             restoreFromStage(lockImageStage, infoStage, "kwp", FLAG_LOCK);
 
             // And reset to the wallpaper service we should be using
-            ComponentName wpService = parseWallpaperComponent(infoStage, "wp");
             updateWallpaperComponent(wpService, !lockImageStage.exists());
         } catch (Exception e) {
             Slog.e(TAG, "Unable to restore wallpaper: " + e.getMessage());
+            mEventLogger.onRestoreException(e);
         } finally {
             Slog.v(TAG, "Restore finished; clearing backup bookkeeping");
             infoStage.delete();
@@ -399,12 +404,15 @@
                 // We have a live wallpaper and no static lock image,
                 // allow live wallpaper to show "through" on lock screen.
                 mWallpaperManager.clear(FLAG_LOCK);
+                mEventLogger.onLockLiveWallpaperRestored(wpService);
             }
+            mEventLogger.onSystemLiveWallpaperRestored(wpService);
         } else {
             // If we've restored a live wallpaper, but the component doesn't exist,
             // we should log it as an error so we can easily identify the problem
             // in reports from users
             if (wpService != null) {
+                // TODO(b/268471749): Handle delayed case
                 applyComponentAtInstall(wpService, applyToLock);
                 Slog.w(TAG, "Wallpaper service " + wpService + " isn't available. "
                         + " Will try to apply later");
@@ -424,13 +432,37 @@
                 try (FileInputStream in = new FileInputStream(stage)) {
                     mWallpaperManager.setStream(in, cropHint.isEmpty() ? null : cropHint, true,
                             which);
+
+                    // And log the success
+                    if ((which & FLAG_SYSTEM) > 0) {
+                        mEventLogger.onSystemImageWallpaperRestored();
+                    } else {
+                        mEventLogger.onLockImageWallpaperRestored();
+                    }
                 }
+            } else {
+                logRestoreError(which, ERROR_NO_METADATA);
             }
         } else {
             Slog.d(TAG, "Restore data doesn't exist for file " + stage.getPath());
+            logRestoreErrorIfNoLiveComponent(which, ERROR_NO_WALLPAPER);
         }
     }
 
+    private void logRestoreErrorIfNoLiveComponent(int which, String error) {
+        if (mSystemHasLiveComponent) {
+            return;
+        }
+        logRestoreError(which, error);
+    }
+
+    private void logRestoreError(int which, String error) {
+        if ((which & FLAG_SYSTEM) == FLAG_SYSTEM) {
+            mEventLogger.onSystemImageWallpaperRestoreFailed(error);
+        } else if ((which & FLAG_LOCK) == FLAG_LOCK) {
+            mEventLogger.onLockImageWallpaperRestoreFailed(error);
+        }
+    }
     private Rect parseCropHint(File wallpaperInfo, String sectionTag) {
         Rect cropHint = new Rect();
         try (FileInputStream stream = new FileInputStream(wallpaperInfo)) {
diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperEventLogger.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperEventLogger.java
index 64944b3..47c45ac 100644
--- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperEventLogger.java
+++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperEventLogger.java
@@ -22,6 +22,7 @@
 import android.app.backup.BackupRestoreEventLogger;
 import android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType;
 import android.app.backup.BackupRestoreEventLogger.BackupRestoreError;
+import android.content.ComponentName;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -101,6 +102,39 @@
         logBackupFailureInternal(WALLPAPER_LIVE_LOCK, error);
     }
 
+    void onSystemImageWallpaperRestored() {
+        logRestoreSuccessInternal(WALLPAPER_IMG_SYSTEM, /* liveComponentWallpaperInfo */ null);
+    }
+
+    void onLockImageWallpaperRestored() {
+        logRestoreSuccessInternal(WALLPAPER_IMG_LOCK, /* liveComponentWallpaperInfo */ null);
+    }
+
+    void onSystemLiveWallpaperRestored(ComponentName wpService) {
+        logRestoreSuccessInternal(WALLPAPER_LIVE_SYSTEM, wpService);
+    }
+
+    void onLockLiveWallpaperRestored(ComponentName wpService) {
+        logRestoreSuccessInternal(WALLPAPER_LIVE_LOCK, wpService);
+    }
+
+    void onSystemImageWallpaperRestoreFailed(@BackupRestoreError String error) {
+        logRestoreFailureInternal(WALLPAPER_IMG_SYSTEM, error);
+    }
+
+    void onLockImageWallpaperRestoreFailed(@BackupRestoreError String error) {
+        logRestoreFailureInternal(WALLPAPER_IMG_LOCK, error);
+    }
+
+    void onSystemLiveWallpaperRestoreFailed(@BackupRestoreError String error) {
+        logRestoreFailureInternal(WALLPAPER_LIVE_SYSTEM, error);
+    }
+
+    void onLockLiveWallpaperRestoreFailed(@BackupRestoreError String error) {
+        logRestoreFailureInternal(WALLPAPER_LIVE_LOCK, error);
+    }
+
+
 
     /**
      * Called when the whole backup flow is interrupted by an exception.
@@ -117,6 +151,20 @@
         }
     }
 
+    /**
+     * Called when the whole restore flow is interrupted by an exception.
+     */
+    void onRestoreException(Exception exception) {
+        String error = exception.getClass().getName();
+        if (!mProcessedDataTypes.contains(WALLPAPER_IMG_SYSTEM) && !mProcessedDataTypes.contains(
+                WALLPAPER_LIVE_SYSTEM)) {
+            mLogger.logItemsRestoreFailed(WALLPAPER_IMG_SYSTEM, /* count */ 1, error);
+        }
+        if (!mProcessedDataTypes.contains(WALLPAPER_IMG_LOCK) && !mProcessedDataTypes.contains(
+                WALLPAPER_LIVE_LOCK)) {
+            mLogger.logItemsRestoreFailed(WALLPAPER_IMG_LOCK, /* count */ 1, error);
+        }
+    }
     private void logBackupSuccessInternal(@BackupRestoreDataType String which,
             @Nullable WallpaperInfo liveComponentWallpaperInfo) {
         mLogger.logItemsBackedUp(which, /* count */ 1);
@@ -130,10 +178,30 @@
         mProcessedDataTypes.add(which);
     }
 
+    private void logRestoreSuccessInternal(@BackupRestoreDataType String which,
+            @Nullable ComponentName liveComponentWallpaperInfo) {
+        mLogger.logItemsRestored(which, /* count */ 1);
+        logRestoredLiveWallpaperNameIfPresent(which, liveComponentWallpaperInfo);
+        mProcessedDataTypes.add(which);
+    }
+
+    private void logRestoreFailureInternal(@BackupRestoreDataType String which,
+            @BackupRestoreError String error) {
+        mLogger.logItemsRestoreFailed(which, /* count */ 1, error);
+        mProcessedDataTypes.add(which);
+    }
+
     private void logLiveWallpaperNameIfPresent(@BackupRestoreDataType String wallpaperType,
             WallpaperInfo wallpaperInfo) {
         if (wallpaperInfo != null) {
             mLogger.logBackupMetadata(wallpaperType, wallpaperInfo.getComponent().getClassName());
         }
     }
+
+    private void logRestoredLiveWallpaperNameIfPresent(@BackupRestoreDataType String wallpaperType,
+            ComponentName wpService) {
+        if (wpService != null) {
+            mLogger.logRestoreMetadata(wallpaperType, wpService.getClassName());
+        }
+    }
 }
diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
index 89459f6..58f6477 100644
--- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
+++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
@@ -24,6 +24,7 @@
 import static com.android.wallpaperbackup.WallpaperBackupAgent.SYSTEM_WALLPAPER_STAGE;
 import static com.android.wallpaperbackup.WallpaperBackupAgent.WALLPAPER_INFO_STAGE;
 import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_INELIGIBLE;
+import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_NO_METADATA;
 import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_NO_WALLPAPER;
 import static com.android.wallpaperbackup.WallpaperEventLogger.ERROR_QUOTA_EXCEEDED;
 import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_IMG_LOCK;
@@ -34,6 +35,8 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
@@ -55,12 +58,14 @@
 import android.os.ParcelFileDescriptor;
 import android.os.UserHandle;
 import android.service.wallpaper.WallpaperService;
+import android.util.Xml;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.content.PackageMonitor;
+import com.android.modules.utils.TypedXmlSerializer;
 import com.android.wallpaperbackup.utils.ContextWithServiceOverrides;
 
 import org.junit.After;
@@ -592,6 +597,123 @@
         assertThat(result.getSuccessCount()).isEqualTo(1);
     }
 
+    @Test
+    public void testOnRestore_systemWallpaperImgSuccess_logsSuccess() throws Exception {
+        mockStagedWallpaperFile(WALLPAPER_INFO_STAGE);
+        mockStagedWallpaperFile(SYSTEM_WALLPAPER_STAGE);
+        mWallpaperBackupAgent.onCreate(USER_HANDLE, BackupAnnotations.BackupDestination.CLOUD,
+                BackupAnnotations.OperationType.RESTORE);
+
+        mWallpaperBackupAgent.onRestoreFinished();
+
+        DataTypeResult result = getLoggingResult(WALLPAPER_IMG_SYSTEM,
+                mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
+        assertThat(result).isNotNull();
+        assertThat(result.getSuccessCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void testOnRestore_lockWallpaperImgSuccess_logsSuccess() throws Exception {
+        mockStagedWallpaperFile(WALLPAPER_INFO_STAGE);
+        mockStagedWallpaperFile(LOCK_WALLPAPER_STAGE);
+        mWallpaperBackupAgent.onCreate(USER_HANDLE, BackupAnnotations.BackupDestination.CLOUD,
+                BackupAnnotations.OperationType.RESTORE);
+
+        mWallpaperBackupAgent.onRestoreFinished();
+
+        DataTypeResult result = getLoggingResult(WALLPAPER_IMG_LOCK,
+                mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
+        assertThat(result).isNotNull();
+        assertThat(result.getSuccessCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void testOnRestore_systemWallpaperImgMissingAndNoLive_logsFailure() throws Exception {
+        mockStagedWallpaperFile(WALLPAPER_INFO_STAGE);
+        mockStagedWallpaperFile(LOCK_WALLPAPER_STAGE);
+        mWallpaperBackupAgent.onCreate(USER_HANDLE, BackupAnnotations.BackupDestination.CLOUD,
+                BackupAnnotations.OperationType.RESTORE);
+
+        mWallpaperBackupAgent.onRestoreFinished();
+
+        DataTypeResult result = getLoggingResult(WALLPAPER_IMG_SYSTEM,
+                mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
+        assertThat(result).isNotNull();
+        assertThat(result.getFailCount()).isEqualTo(1);
+        assertThat(result.getErrors()).containsKey(ERROR_NO_WALLPAPER);
+
+    }
+
+    @Test
+    public void testOnRestore_lockWallpaperImgMissingAndNoLive_logsFailure() throws Exception {
+        mockStagedWallpaperFile(WALLPAPER_INFO_STAGE);
+        mockStagedWallpaperFile(SYSTEM_WALLPAPER_STAGE);
+        mWallpaperBackupAgent.onCreate(USER_HANDLE, BackupAnnotations.BackupDestination.CLOUD,
+                BackupAnnotations.OperationType.RESTORE);
+
+        mWallpaperBackupAgent.onRestoreFinished();
+
+        DataTypeResult result = getLoggingResult(WALLPAPER_IMG_LOCK,
+                mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
+        assertThat(result).isNotNull();
+        assertThat(result.getFailCount()).isEqualTo(1);
+        assertThat(result.getErrors()).containsKey(ERROR_NO_WALLPAPER);
+    }
+
+    @Test
+    public void testOnRestore_wallpaperInfoMissing_logsFailure() throws Exception {
+        mockStagedWallpaperFile(SYSTEM_WALLPAPER_STAGE);
+        mWallpaperBackupAgent.onCreate(USER_HANDLE, BackupAnnotations.BackupDestination.CLOUD,
+                BackupAnnotations.OperationType.RESTORE);
+
+        mWallpaperBackupAgent.onRestoreFinished();
+
+        DataTypeResult result = getLoggingResult(WALLPAPER_IMG_SYSTEM,
+                mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
+        assertThat(result).isNotNull();
+        assertThat(result.getFailCount()).isEqualTo(1);
+        assertThat(result.getErrors()).containsKey(ERROR_NO_METADATA);
+    }
+
+    @Test
+    public void testOnRestore_imgMissingButWallpaperInfoHasLive_doesNotLogImg() throws Exception {
+        mockRestoredLiveWallpaperFile();
+        mWallpaperBackupAgent.onCreate(USER_HANDLE, BackupAnnotations.BackupDestination.CLOUD,
+                BackupAnnotations.OperationType.RESTORE);
+
+        mWallpaperBackupAgent.onRestoreFinished();
+
+        DataTypeResult system = getLoggingResult(WALLPAPER_IMG_SYSTEM,
+                mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
+        DataTypeResult lock = getLoggingResult(WALLPAPER_IMG_LOCK,
+                mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
+        assertThat(system).isNull();
+        assertThat(lock).isNull();
+    }
+
+    @Test
+    public void testOnRestore_throwsException_logsErrors() throws Exception {
+        when(mWallpaperManager.setStream(any(), any(), anyBoolean(), anyInt())).thenThrow(
+                new RuntimeException());
+        mockStagedWallpaperFile(SYSTEM_WALLPAPER_STAGE);
+        mockStagedWallpaperFile(WALLPAPER_INFO_STAGE);
+        mWallpaperBackupAgent.onCreate(USER_HANDLE, BackupAnnotations.BackupDestination.CLOUD,
+                BackupAnnotations.OperationType.RESTORE);
+
+        mWallpaperBackupAgent.onRestoreFinished();
+
+        DataTypeResult system = getLoggingResult(WALLPAPER_IMG_SYSTEM,
+                mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
+        DataTypeResult lock = getLoggingResult(WALLPAPER_IMG_LOCK,
+                mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
+        assertThat(system).isNotNull();
+        assertThat(system.getFailCount()).isEqualTo(1);
+        assertThat(system.getErrors()).containsKey(RuntimeException.class.getName());
+        assertThat(lock).isNotNull();
+        assertThat(lock.getFailCount()).isEqualTo(1);
+        assertThat(lock.getErrors()).containsKey(RuntimeException.class.getName());
+    }
+
     private void mockCurrentWallpaperIds(int systemWallpaperId, int lockWallpaperId) {
         when(mWallpaperManager.getWallpaperId(eq(FLAG_SYSTEM))).thenReturn(systemWallpaperId);
         when(mWallpaperManager.getWallpaperId(eq(FLAG_LOCK))).thenReturn(lockWallpaperId);
@@ -636,6 +758,27 @@
                 ParcelFileDescriptor.open(fakeLockWallpaperFile, MODE_READ_ONLY));
     }
 
+    private void mockStagedWallpaperFile(String location) throws Exception {
+        File wallpaperFile = new File(mContext.getFilesDir(), location);
+        wallpaperFile.createNewFile();
+    }
+
+    private void mockRestoredLiveWallpaperFile() throws Exception {
+        File wallpaperFile = new File(mContext.getFilesDir(), WALLPAPER_INFO_STAGE);
+        wallpaperFile.createNewFile();
+        FileOutputStream fstream = new FileOutputStream(wallpaperFile, false);
+        TypedXmlSerializer out = Xml.resolveSerializer(fstream);
+        out.startDocument(null, true);
+        out.startTag(null, "wp");
+        out.attribute(null, "component",
+                getFakeWallpaperInfo().getComponent().flattenToShortString());
+        out.endTag(null, "wp");
+        out.endDocument();
+        fstream.flush();
+        FileUtils.sync(fstream);
+        fstream.close();
+    }
+
     private WallpaperInfo getFakeWallpaperInfo() throws Exception {
         Context context = InstrumentationRegistry.getTargetContext();
         Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE);
diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperEventLoggerTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperEventLoggerTest.java
index 3816a3c..383bf2f 100644
--- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperEventLoggerTest.java
+++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperEventLoggerTest.java
@@ -21,16 +21,14 @@
 import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_LIVE_LOCK;
 import static com.android.wallpaperbackup.WallpaperEventLogger.WALLPAPER_LIVE_SYSTEM;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.WallpaperInfo;
+import android.app.backup.BackupAnnotations;
 import android.app.backup.BackupManager;
 import android.app.backup.BackupRestoreEventLogger;
 import android.content.Context;
@@ -42,8 +40,6 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.wallpaperbackup.utils.TestWallpaperService;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -55,8 +51,7 @@
 @RunWith(AndroidJUnit4.class)
 public class WallpaperEventLoggerTest {
 
-    @Mock
-    private BackupRestoreEventLogger mMockLogger;
+    private BackupRestoreEventLogger mEventLogger;
 
     @Mock
     private BackupManager mMockBackupManager;
@@ -73,8 +68,8 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
-        when(mMockBackupAgent.getBackupRestoreEventLogger()).thenReturn(mMockLogger);
-        when(mMockBackupManager.getBackupRestoreEventLogger(any())).thenReturn(mMockLogger);
+        when(mMockBackupAgent.getBackupRestoreEventLogger()).thenReturn(mEventLogger);
+        when(mMockBackupManager.getBackupRestoreEventLogger(any())).thenReturn(mEventLogger);
 
         mWallpaperInfo = getWallpaperInfo();
         mWallpaperEventLogger = new WallpaperEventLogger(mMockBackupManager, mMockBackupAgent);
@@ -82,115 +77,339 @@
 
     @Test
     public void onSystemImgWallpaperBackedUp_logsSuccess() {
-        mWallpaperEventLogger.onSystemImageWallpaperBackedUp();
+        setUpLoggerForBackup();
 
-        verify(mMockLogger).logItemsBackedUp(eq(WALLPAPER_IMG_SYSTEM), eq(1));
+        mWallpaperEventLogger.onSystemImageWallpaperBackedUp();
+        BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_IMG_SYSTEM);
+
+        assertThat(result).isNotNull();
+        assertThat(result.getSuccessCount()).isEqualTo(1);
     }
 
     @Test
     public void onLockImgWallpaperBackedUp_logsSuccess() {
-        mWallpaperEventLogger.onLockImageWallpaperBackedUp();
+        setUpLoggerForBackup();
 
-        verify(mMockLogger).logItemsBackedUp(eq(WALLPAPER_IMG_LOCK), eq(1));
+        mWallpaperEventLogger.onLockImageWallpaperBackedUp();
+        BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_IMG_LOCK);
+
+        assertThat(result).isNotNull();
+        assertThat(result.getSuccessCount()).isEqualTo(1);
     }
 
     @Test
     public void onSystemLiveWallpaperBackedUp_logsSuccess() {
-        mWallpaperEventLogger.onSystemLiveWallpaperBackedUp(mWallpaperInfo);
+        setUpLoggerForBackup();
 
-        verify(mMockLogger).logItemsBackedUp(eq(WALLPAPER_LIVE_SYSTEM), eq(1));
+        mWallpaperEventLogger.onSystemLiveWallpaperBackedUp(mWallpaperInfo);
+        BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_LIVE_SYSTEM);
+
+        assertThat(result).isNotNull();
+        assertThat(result.getSuccessCount()).isEqualTo(1);
     }
 
     @Test
     public void onLockLiveWallpaperBackedUp_logsSuccess() {
-        mWallpaperEventLogger.onLockLiveWallpaperBackedUp(mWallpaperInfo);
+        setUpLoggerForBackup();
 
-        verify(mMockLogger).logItemsBackedUp(eq(WALLPAPER_LIVE_LOCK), eq(1));
+        mWallpaperEventLogger.onLockLiveWallpaperBackedUp(mWallpaperInfo);
+        BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_LIVE_LOCK);
+
+        assertThat(result).isNotNull();
+        assertThat(result.getSuccessCount()).isEqualTo(1);
     }
 
     @Test
     public void onImgWallpaperBackedUp_nullInfo_doesNotLogMetadata() {
-        mWallpaperEventLogger.onSystemImageWallpaperBackedUp();
+        setUpLoggerForBackup();
 
-        verify(mMockLogger, never()).logBackupMetadata(eq(WALLPAPER_IMG_SYSTEM), anyString());
+        mWallpaperEventLogger.onSystemImageWallpaperBackedUp();
+        BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_IMG_SYSTEM);
+
+        assertThat(result).isNotNull();
+        assertThat(result.getMetadataHash()).isNull();
     }
 
 
     @Test
     public void onLiveWallpaperBackedUp_logsMetadata() {
-        mWallpaperEventLogger.onSystemLiveWallpaperBackedUp(mWallpaperInfo);
+        setUpLoggerForBackup();
 
-        verify(mMockLogger).logBackupMetadata(eq(WALLPAPER_LIVE_SYSTEM),
-                eq(TestWallpaperService.class.getName()));
+        mWallpaperEventLogger.onSystemLiveWallpaperBackedUp(mWallpaperInfo);
+        BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_LIVE_SYSTEM);
+
+        assertThat(result).isNotNull();
+        assertThat(result.getMetadataHash()).isNotNull();
     }
 
 
     @Test
     public void onSystemImgWallpaperBackupFailed_logsFail() {
-        mWallpaperEventLogger.onSystemImageWallpaperBackupFailed(WALLPAPER_ERROR);
+        setUpLoggerForBackup();
 
-        verify(mMockLogger).logItemsBackupFailed(eq(WALLPAPER_IMG_SYSTEM), eq(1),
-                eq(WALLPAPER_ERROR));
+        mWallpaperEventLogger.onSystemImageWallpaperBackupFailed(WALLPAPER_ERROR);
+        BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_IMG_SYSTEM);
+
+        assertThat(result).isNotNull();
+        assertThat(result.getFailCount()).isEqualTo(1);
+        assertThat(result.getErrors()).containsKey(WALLPAPER_ERROR);
     }
 
     @Test
     public void onLockImgWallpaperBackupFailed_logsFail() {
-        mWallpaperEventLogger.onLockImageWallpaperBackupFailed(WALLPAPER_ERROR);
+        setUpLoggerForBackup();
 
-        verify(mMockLogger).logItemsBackupFailed(eq(WALLPAPER_IMG_LOCK), eq(1),
-                eq(WALLPAPER_ERROR));
+        mWallpaperEventLogger.onLockImageWallpaperBackupFailed(WALLPAPER_ERROR);
+        BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_IMG_LOCK);
+
+        assertThat(result).isNotNull();
+        assertThat(result.getFailCount()).isEqualTo(1);
+        assertThat(result.getErrors()).containsKey(WALLPAPER_ERROR);
     }
 
 
     @Test
     public void onSystemLiveWallpaperBackupFailed_logsFail() {
-        mWallpaperEventLogger.onSystemLiveWallpaperBackupFailed(WALLPAPER_ERROR);
+        setUpLoggerForBackup();
 
-        verify(mMockLogger).logItemsBackupFailed(eq(WALLPAPER_LIVE_SYSTEM), eq(1),
-                eq(WALLPAPER_ERROR));
+        mWallpaperEventLogger.onSystemLiveWallpaperBackupFailed(WALLPAPER_ERROR);
+        BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_LIVE_SYSTEM);
+
+        assertThat(result).isNotNull();
+        assertThat(result.getFailCount()).isEqualTo(1);
+        assertThat(result.getErrors()).containsKey(WALLPAPER_ERROR);
     }
 
     @Test
     public void onLockLiveWallpaperBackupFailed_logsFail() {
-        mWallpaperEventLogger.onLockLiveWallpaperBackupFailed(WALLPAPER_ERROR);
+        setUpLoggerForBackup();
 
-        verify(mMockLogger).logItemsBackupFailed(eq(WALLPAPER_LIVE_LOCK), eq(1),
-                eq(WALLPAPER_ERROR));
+        mWallpaperEventLogger.onLockLiveWallpaperBackupFailed(WALLPAPER_ERROR);
+        BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_LIVE_LOCK);
+
+        assertThat(result).isNotNull();
+        assertThat(result.getFailCount()).isEqualTo(1);
+        assertThat(result.getErrors()).containsKey(WALLPAPER_ERROR);
     }
 
 
     @Test
     public void onWallpaperBackupException_someProcessed_doesNotLogErrorForProcessedType() {
+        setUpLoggerForBackup();
         mWallpaperEventLogger.onSystemImageWallpaperBackedUp();
 
         mWallpaperEventLogger.onBackupException(new Exception());
+        BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_IMG_SYSTEM);
 
-        verify(mMockLogger, never()).logItemsBackupFailed(eq(WALLPAPER_IMG_SYSTEM), anyInt(),
-                anyString());
+        assertThat(result).isNotNull();
+        assertThat(result.getFailCount()).isEqualTo(0);
     }
 
 
     @Test
     public void onWallpaperBackupException_someProcessed_logsErrorForUnprocessedType() {
+        setUpLoggerForBackup();
         mWallpaperEventLogger.onSystemImageWallpaperBackedUp();
 
         mWallpaperEventLogger.onBackupException(new Exception());
+        BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_IMG_LOCK);
 
-        verify(mMockLogger).logItemsBackupFailed(eq(WALLPAPER_IMG_LOCK), eq(1),
-                eq(Exception.class.getName()));
-
+        assertThat(result).isNotNull();
+        assertThat(result.getFailCount()).isEqualTo(1);
     }
 
     @Test
-    public void onWallpaperBackupException_liveTypeProcessed_doesNotLogErrorForSameImgType() {
+    public void onWallpaperBackupException_liveTypeProcessed_doesNotLogForSameImgType() {
+        setUpLoggerForBackup();
         mWallpaperEventLogger.onSystemLiveWallpaperBackedUp(mWallpaperInfo);
 
         mWallpaperEventLogger.onBackupException(new Exception());
+        BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_IMG_SYSTEM);
 
-        verify(mMockLogger, never()).logItemsBackupFailed(eq(WALLPAPER_IMG_SYSTEM), anyInt(),
-                anyString());
+        assertThat(result).isNull();
     }
 
+    @Test
+    public void onSystemImgWallpaperRestored_logsSuccess() {
+        setUpLoggerForRestore();
+
+        mWallpaperEventLogger.onSystemImageWallpaperRestored();
+        BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_IMG_SYSTEM);
+
+        assertThat(result).isNotNull();
+        assertThat(result.getSuccessCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void onLockImgWallpaperRestored_logsSuccess() {
+        setUpLoggerForRestore();
+
+        mWallpaperEventLogger.onLockImageWallpaperRestored();
+        BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_IMG_LOCK);
+
+        assertThat(result).isNotNull();
+        assertThat(result.getSuccessCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void onSystemLiveWallpaperRestored_logsSuccess() {
+        setUpLoggerForRestore();
+
+        mWallpaperEventLogger.onSystemLiveWallpaperRestored(mWallpaperInfo.getComponent());
+        BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_LIVE_SYSTEM);
+
+        assertThat(result).isNotNull();
+        assertThat(result.getSuccessCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void onLockLiveWallpaperRestored_logsSuccess() {
+        setUpLoggerForRestore();
+
+        mWallpaperEventLogger.onLockLiveWallpaperRestored(mWallpaperInfo.getComponent());
+        BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_LIVE_LOCK);
+
+        assertThat(result).isNotNull();
+        assertThat(result.getSuccessCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void onImgWallpaperRestored_nullInfo_doesNotLogMetadata() {
+        setUpLoggerForRestore();
+
+        mWallpaperEventLogger.onSystemImageWallpaperRestored();
+        BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_IMG_SYSTEM);
+
+        assertThat(result).isNotNull();
+        assertThat(result.getMetadataHash()).isNull();
+    }
+
+
+    @Test
+    public void onLiveWallpaperRestored_logsMetadata() {
+        setUpLoggerForRestore();
+
+        mWallpaperEventLogger.onSystemLiveWallpaperRestored(mWallpaperInfo.getComponent());
+        BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_LIVE_SYSTEM);
+
+        assertThat(result).isNotNull();
+        assertThat(result.getMetadataHash()).isNotNull();
+    }
+
+
+    @Test
+    public void onSystemImgWallpaperRestoreFailed_logsFail() {
+        setUpLoggerForRestore();
+
+        mWallpaperEventLogger.onSystemImageWallpaperRestoreFailed(WALLPAPER_ERROR);
+        BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_IMG_SYSTEM);
+
+        assertThat(result).isNotNull();
+        assertThat(result.getFailCount()).isEqualTo(1);
+        assertThat(result.getErrors()).containsKey(WALLPAPER_ERROR);
+    }
+
+    @Test
+    public void onLockImgWallpaperRestoreFailed_logsFail() {
+        setUpLoggerForRestore();
+
+        mWallpaperEventLogger.onLockImageWallpaperRestoreFailed(WALLPAPER_ERROR);
+        BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_IMG_LOCK);
+
+        assertThat(result).isNotNull();
+        assertThat(result.getFailCount()).isEqualTo(1);
+        assertThat(result.getErrors()).containsKey(WALLPAPER_ERROR);
+    }
+
+
+    @Test
+    public void onSystemLiveWallpaperRestoreFailed_logsFail() {
+        setUpLoggerForRestore();
+
+        mWallpaperEventLogger.onSystemLiveWallpaperRestoreFailed(WALLPAPER_ERROR);
+        BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_LIVE_SYSTEM);
+
+        assertThat(result).isNotNull();
+        assertThat(result.getFailCount()).isEqualTo(1);
+        assertThat(result.getErrors()).containsKey(WALLPAPER_ERROR);
+    }
+
+    @Test
+    public void onLockLiveWallpaperRestoreFailed_logsFail() {
+        setUpLoggerForRestore();
+
+        mWallpaperEventLogger.onLockLiveWallpaperRestoreFailed(WALLPAPER_ERROR);
+        BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_LIVE_LOCK);
+
+        assertThat(result).isNotNull();
+        assertThat(result.getFailCount()).isEqualTo(1);
+        assertThat(result.getErrors()).containsKey(WALLPAPER_ERROR);
+    }
+
+
+    @Test
+    public void onWallpaperRestoreException_someProcessed_doesNotLogErrorForProcessedType() {
+        setUpLoggerForRestore();
+        mWallpaperEventLogger.onSystemImageWallpaperRestored();
+
+        mWallpaperEventLogger.onRestoreException(new Exception());
+        BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_IMG_SYSTEM);
+
+        assertThat(result).isNotNull();
+        assertThat(result.getFailCount()).isEqualTo(0);
+    }
+
+
+    @Test
+    public void onWallpaperRestoreException_someProcessed_logsErrorForUnprocessedType() {
+        setUpLoggerForRestore();
+        mWallpaperEventLogger.onSystemImageWallpaperRestored();
+
+        mWallpaperEventLogger.onRestoreException(new Exception());
+        BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_IMG_LOCK);
+
+        assertThat(result).isNotNull();
+        assertThat(result.getFailCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void onWallpaperRestoreException_liveTypeProcessed_doesNotLogForSameImgType() {
+        setUpLoggerForRestore();
+        mWallpaperEventLogger.onSystemLiveWallpaperRestored(mWallpaperInfo.getComponent());
+
+        mWallpaperEventLogger.onRestoreException(new Exception());
+        BackupRestoreEventLogger.DataTypeResult result = getLogsForType(WALLPAPER_IMG_SYSTEM);
+
+        assertThat(result).isNull();
+    }
+
+    private BackupRestoreEventLogger.DataTypeResult getLogsForType(String dataType) {
+        for (BackupRestoreEventLogger.DataTypeResult result :  mEventLogger.getLoggingResults()) {
+            if ((result.getDataType()).equals(dataType)) {
+                return result;
+            }
+        }
+        return null;
+    }
+
+    private void setUpLoggerForBackup() {
+        mEventLogger = new BackupRestoreEventLogger(BackupAnnotations.OperationType.BACKUP);
+        createEventLogger();
+    }
+
+    private void setUpLoggerForRestore() {
+        mEventLogger = new BackupRestoreEventLogger(BackupAnnotations.OperationType.RESTORE);
+        createEventLogger();
+    }
+
+    private void createEventLogger() {
+        when(mMockBackupAgent.getBackupRestoreEventLogger()).thenReturn(mEventLogger);
+        when(mMockBackupManager.getBackupRestoreEventLogger(any())).thenReturn(mEventLogger);
+
+        mWallpaperEventLogger = new WallpaperEventLogger(mMockBackupManager, mMockBackupAgent);
+    }
+
+
     private WallpaperInfo getWallpaperInfo() throws Exception {
         Context context = InstrumentationRegistry.getTargetContext();
         Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE);
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index fbc7b3c..874fb01 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -1072,6 +1072,10 @@
         }
     }
 
+    protected float getLastActivatedScale(int displayId) {
+        return getScale(displayId);
+    }
+
     /**
      * Returns the X offset of the magnification viewport. If an animation
      * is in progress, this reflects the end state of the animation.
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index 7ee72df..fee20c8 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -470,12 +470,14 @@
             disableFullScreenMagnificationIfNeeded(displayId);
         } else {
             long duration;
+            float scale;
             synchronized (mLock) {
                 setCurrentMagnificationModeAndSwitchDelegate(displayId,
                         ACCESSIBILITY_MAGNIFICATION_MODE_NONE);
                 duration = SystemClock.uptimeMillis() - mWindowModeEnabledTimeArray.get(displayId);
+                scale = mWindowMagnificationMgr.getLastActivatedScale(displayId);
             }
-            logMagnificationUsageState(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW, duration);
+            logMagnificationUsageState(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW, duration, scale);
         }
         updateMagnificationUIControls(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
     }
@@ -567,13 +569,16 @@
             disableWindowMagnificationIfNeeded(displayId);
         } else {
             long duration;
+            float scale;
             synchronized (mLock) {
                 setCurrentMagnificationModeAndSwitchDelegate(displayId,
                         ACCESSIBILITY_MAGNIFICATION_MODE_NONE);
                 duration = SystemClock.uptimeMillis()
                         - mFullScreenModeEnabledTimeArray.get(displayId);
+                scale = mFullScreenMagnificationController.getLastActivatedScale(displayId);
             }
-            logMagnificationUsageState(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN, duration);
+            logMagnificationUsageState(
+                    ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN, duration, scale);
         }
         updateMagnificationUIControls(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
     }
@@ -612,10 +617,11 @@
      *
      * @param mode The activated magnification mode.
      * @param duration The duration in milliseconds during the magnification is activated.
+     * @param scale The last magnification scale for the activation
      */
     @VisibleForTesting
-    public void logMagnificationUsageState(int mode, long duration) {
-        AccessibilityStatsLogUtils.logMagnificationUsageState(mode, duration);
+    public void logMagnificationUsageState(int mode, long duration, float scale) {
+        AccessibilityStatsLogUtils.logMagnificationUsageState(mode, duration, scale);
     }
 
     /**
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
index ce18b2c..d07db3f 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
@@ -142,6 +142,8 @@
     private boolean mMagnificationFollowTypingEnabled = true;
     @GuardedBy("mLock")
     private final SparseBooleanArray mIsImeVisibleArray = new SparseBooleanArray();
+    @GuardedBy("mLock")
+    private final SparseArray<Float> mLastActivatedScale = new SparseArray<>();
 
     private boolean mReceiverRegistered = false;
     @VisibleForTesting
@@ -528,6 +530,7 @@
                 return;
             }
             magnifier.setScale(scale);
+            mLastActivatedScale.put(displayId, scale);
         }
     }
 
@@ -615,6 +618,9 @@
             previousEnabled = magnifier.mEnabled;
             enabled = magnifier.enableWindowMagnificationInternal(scale, centerX, centerY,
                     animationCallback, windowPosition, id);
+            if (enabled) {
+                mLastActivatedScale.put(displayId, getScale(displayId));
+            }
         }
 
         if (enabled) {
@@ -752,6 +758,15 @@
         }
     }
 
+    protected float getLastActivatedScale(int displayId) {
+        synchronized (mLock) {
+            if (!mLastActivatedScale.contains(displayId)) {
+                return -1.0f;
+            }
+            return mLastActivatedScale.get(displayId);
+        }
+    }
+
     /**
      * Moves window magnification on the specified display with the specified offset.
      *
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index fb94af6..3113000 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -1726,6 +1726,7 @@
                     }
                 }
             }
+            if (ids.isEmpty()) return saveInfo;
             AutofillId[] autofillIds = new AutofillId[ids.size()];
             ids.toArray(autofillIds);
             return SaveInfo.copy(saveInfo, autofillIds);
diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
index e9cd84a..77275e0 100644
--- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
@@ -288,7 +288,7 @@
         final AssociationInfo association = new AssociationInfo(id, userId, packageName,
                 macAddress, displayName, deviceProfile, associatedDevice, selfManaged,
                 /* notifyOnDeviceNearby */ false, /* revoked */ false, timestamp, Long.MAX_VALUE,
-                /* systemDataSyncFlags */ ~0);
+                /* systemDataSyncFlags */ 0);
 
         if (deviceProfile != null) {
             // If the "Device Profile" is specified, make the companion application a holder of the
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceConfig.java b/services/companion/java/com/android/server/companion/CompanionDeviceConfig.java
index 05f2eea..8570515 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceConfig.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceConfig.java
@@ -31,10 +31,10 @@
     public static final String ENABLE_CONTEXT_SYNC_TELECOM = "enable_context_sync_telecom";
 
     /**
-     * Returns whether the given flag is currently enabled, with a default value of {@code true}.
+     * Returns whether the given flag is currently enabled, with a default value of {@code false}.
      */
     public static boolean isEnabled(String flag) {
-        return DeviceConfig.getBoolean(NAMESPACE_COMPANION, flag, /* defaultValue= */ true);
+        return DeviceConfig.getBoolean(NAMESPACE_COMPANION, flag, /* defaultValue= */ false);
     }
 
     /**
diff --git a/services/companion/java/com/android/server/companion/PersistentDataStore.java b/services/companion/java/com/android/server/companion/PersistentDataStore.java
index b66c193..54798f4 100644
--- a/services/companion/java/com/android/server/companion/PersistentDataStore.java
+++ b/services/companion/java/com/android/server/companion/PersistentDataStore.java
@@ -133,7 +133,7 @@
  *             revoked="false"
  *             last_time_connected="1634641160229"
  *             time_approved="1634389553216"
- *             system_data_sync_flags="-1"/>
+ *             system_data_sync_flags="0"/>
  *
  *         <association
  *             id="3"
@@ -145,7 +145,7 @@
  *             revoked="false"
  *             last_time_connected="1634641160229"
  *             time_approved="1634641160229"
- *             system_data_sync_flags="-1"/>
+ *             system_data_sync_flags="1"/>
  *     </associations>
  *
  *     <previously-used-ids>
@@ -432,7 +432,7 @@
         out.add(new AssociationInfo(associationId, userId, appPackage,
                 MacAddress.fromString(deviceAddress), null, profile, null,
                 /* managedByCompanionApp */ false, notify, /* revoked */ false, timeApproved,
-                Long.MAX_VALUE, /* systemDataSyncFlags */ -1));
+                Long.MAX_VALUE, /* systemDataSyncFlags */ 0));
     }
 
     private static void readAssociationsV1(@NonNull TypedXmlPullParser parser,
@@ -466,7 +466,7 @@
         final long lastTimeConnected = readLongAttribute(
                 parser, XML_ATTR_LAST_TIME_CONNECTED, Long.MAX_VALUE);
         final int systemDataSyncFlags = readIntAttribute(parser,
-                XML_ATTR_SYSTEM_DATA_SYNC_FLAGS, -1);
+                XML_ATTR_SYSTEM_DATA_SYNC_FLAGS, 0);
 
         final AssociationInfo associationInfo = createAssociationInfoNoThrow(associationId, userId,
                 appPackage, macAddress, displayName, profile, selfManaged, notify, revoked,
diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
index 17bdb60..0f00f5f 100644
--- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
+++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
@@ -19,10 +19,8 @@
 import static android.Manifest.permission.DELIVER_COMPANION_MESSAGES;
 
 import static com.android.server.companion.transport.Transport.MESSAGE_REQUEST_PERMISSION_RESTORE;
-import static com.android.server.companion.transport.Transport.MESSAGE_REQUEST_PLATFORM_INFO;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.app.ActivityManagerInternal;
 import android.companion.AssociationInfo;
@@ -33,7 +31,6 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.os.Binder;
 import android.os.Build;
-import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
@@ -46,7 +43,6 @@
 
 import java.io.FileDescriptor;
 import java.io.IOException;
-import java.nio.ByteBuffer;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.List;
@@ -58,19 +54,8 @@
     private static final String TAG = "CDM_CompanionTransportManager";
     private static final boolean DEBUG = false;
 
-    private static final int SECURE_CHANNEL_AVAILABLE_SDK = Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
-    private static final int NON_ANDROID = -1;
-
     private boolean mSecureTransportEnabled = true;
 
-    private static boolean isRequest(int message) {
-        return (message & 0xFF000000) == 0x63000000;
-    }
-
-    private static boolean isResponse(int message) {
-        return (message & 0xFF000000) == 0x33000000;
-    }
-
     private final Context mContext;
     private final AssociationStore mAssociationStore;
 
@@ -84,10 +69,6 @@
     @NonNull
     private final SparseArray<IOnMessageReceivedListener> mMessageListeners = new SparseArray<>();
 
-
-    @Nullable
-    private Transport mTempTransport;
-
     public CompanionTransportManager(Context context, AssociationStore associationStore) {
         mContext = context;
         mAssociationStore = associationStore;
@@ -199,7 +180,8 @@
                 detachSystemDataTransport(packageName, userId, associationId);
             }
 
-            initializeTransport(associationId, fd);
+            // TODO: Implement new API to pass a PSK
+            initializeTransport(associationId, fd, null);
 
             notifyOnTransportsChanged();
         }
@@ -237,107 +219,36 @@
         });
     }
 
-    private void initializeTransport(int associationId, ParcelFileDescriptor fd) {
+    private void initializeTransport(int associationId,
+                                     ParcelFileDescriptor fd,
+                                     byte[] preSharedKey) {
         Slog.i(TAG, "Initializing transport");
+        Transport transport;
         if (!isSecureTransportEnabled()) {
-            Transport transport = new RawTransport(associationId, fd, mContext);
-            addMessageListenersToTransport(transport);
-            transport.start();
-            synchronized (mTransports) {
-                mTransports.put(associationId, transport);
-            }
-            Slog.i(TAG, "RawTransport is created");
-            return;
-        }
-
-        // Exchange platform info to decide which transport should be created
-        mTempTransport = new RawTransport(associationId, fd, mContext);
-        addMessageListenersToTransport(mTempTransport);
-        IOnMessageReceivedListener listener = new IOnMessageReceivedListener() {
-            @Override
-            public void onMessageReceived(int associationId, byte[] data) throws RemoteException {
-                synchronized (mTransports) {
-                    onPlatformInfoReceived(associationId, data);
-                }
-            }
-
-            @Override
-            public IBinder asBinder() {
-                return null;
-            }
-        };
-        mTempTransport.addListener(MESSAGE_REQUEST_PLATFORM_INFO, listener);
-        mTempTransport.start();
-
-        int sdk = Build.VERSION.SDK_INT;
-        String release = Build.VERSION.RELEASE;
-        // data format: | SDK_INT (int) | release length (int) | release |
-        final ByteBuffer data = ByteBuffer.allocate(4 + 4 + release.getBytes().length)
-                .putInt(sdk)
-                .putInt(release.getBytes().length)
-                .put(release.getBytes());
-
-        // TODO: it should check if preSharedKey is given
-        try {
-            mTempTransport.sendMessage(MESSAGE_REQUEST_PLATFORM_INFO, data.array());
-        } catch (IOException e) {
-            Slog.e(TAG, "Failed to exchange platform info");
-        }
-    }
-
-    /**
-     * Depending on the remote platform info to decide which transport should be created
-     */
-    private void onPlatformInfoReceived(int associationId, byte[] data) {
-        if (mTempTransport.getAssociationId() != associationId) {
-            return;
-        }
-        // TODO: it should check if preSharedKey is given
-
-        ByteBuffer buffer = ByteBuffer.wrap(data);
-        int remoteSdk = buffer.getInt();
-        byte[] remoteRelease = new byte[buffer.getInt()];
-        buffer.get(remoteRelease);
-
-        Slog.i(TAG, "Remote device SDK: " + remoteSdk + ", release:" + new String(remoteRelease));
-
-        Transport transport = mTempTransport;
-        mTempTransport.stop();
-
-        int sdk = Build.VERSION.SDK_INT;
-        String release = Build.VERSION.RELEASE;
-
-        if (sdk < SECURE_CHANNEL_AVAILABLE_SDK || remoteSdk < SECURE_CHANNEL_AVAILABLE_SDK) {
-            // If either device is Android T or below, use raw channel
-            // TODO: depending on the release version, either
-            //       1) using a RawTransport for old T versions
-            //       2) or an Ukey2 handshaked transport for UKey2 backported T versions
-            Slog.d(TAG, "Secure channel is not supported. Using raw transport");
-            transport = new RawTransport(transport.getAssociationId(), transport.getFd(), mContext);
+            // If secure transport is explicitly disabled for testing, use raw transport
+            Slog.i(TAG, "Secure channel is disabled. Creating raw transport");
+            transport = new RawTransport(associationId, fd, mContext);
         } else if (Build.isDebuggable()) {
             // If device is debug build, use hardcoded test key for authentication
             Slog.d(TAG, "Creating an unauthenticated secure channel");
             final byte[] testKey = "CDM".getBytes(StandardCharsets.UTF_8);
-            transport = new SecureTransport(transport.getAssociationId(), transport.getFd(),
-                    mContext, testKey, null);
-        } else if (sdk == NON_ANDROID || remoteSdk == NON_ANDROID) {
+            transport = new SecureTransport(associationId, fd, mContext, testKey, null);
+        } else if (preSharedKey != null) {
             // If either device is not Android, then use app-specific pre-shared key
-            // TODO: pass in a real preSharedKey
             Slog.d(TAG, "Creating a PSK-authenticated secure channel");
-            transport = new SecureTransport(transport.getAssociationId(), transport.getFd(),
-                    mContext, new byte[0], null);
+            transport = new SecureTransport(associationId, fd, mContext, preSharedKey, null);
         } else {
             // If none of the above applies, then use secure channel with attestation verification
             Slog.d(TAG, "Creating a secure channel");
-            transport = new SecureTransport(transport.getAssociationId(), transport.getFd(),
-                    mContext);
+            transport = new SecureTransport(associationId, fd, mContext);
         }
+
         addMessageListenersToTransport(transport);
         transport.start();
         synchronized (mTransports) {
-            mTransports.put(transport.getAssociationId(), transport);
+            mTransports.put(associationId, transport);
         }
-        // Doesn't need to notifyTransportsChanged here, it'll be done in attachSystemDataTransport
+
     }
 
     public Future<?> requestPermissionRestore(int associationId, byte[] data) {
diff --git a/services/companion/java/com/android/server/companion/transport/RawTransport.java b/services/companion/java/com/android/server/companion/transport/RawTransport.java
index 4158901..e64509f 100644
--- a/services/companion/java/com/android/server/companion/transport/RawTransport.java
+++ b/services/companion/java/com/android/server/companion/transport/RawTransport.java
@@ -35,7 +35,7 @@
     }
 
     @Override
-    public void start() {
+    void start() {
         if (DEBUG) {
             Slog.d(TAG, "Starting raw transport.");
         }
@@ -54,7 +54,7 @@
     }
 
     @Override
-    public void stop() {
+    void stop() {
         if (DEBUG) {
             Slog.d(TAG, "Stopping raw transport.");
         }
@@ -62,7 +62,7 @@
     }
 
     @Override
-    public void close() {
+    void close() {
         stop();
 
         if (DEBUG) {
diff --git a/services/companion/java/com/android/server/companion/transport/SecureTransport.java b/services/companion/java/com/android/server/companion/transport/SecureTransport.java
index 4054fc9..949f39a 100644
--- a/services/companion/java/com/android/server/companion/transport/SecureTransport.java
+++ b/services/companion/java/com/android/server/companion/transport/SecureTransport.java
@@ -51,18 +51,18 @@
     }
 
     @Override
-    public void start() {
+    void start() {
         mSecureChannel.start();
     }
 
     @Override
-    public void stop() {
+    void stop() {
         mSecureChannel.stop();
         mShouldProcessRequests = false;
     }
 
     @Override
-    public void close() {
+    void close() {
         mSecureChannel.close();
         mShouldProcessRequests = false;
     }
diff --git a/services/companion/java/com/android/server/companion/transport/Transport.java b/services/companion/java/com/android/server/companion/transport/Transport.java
index d30104a..6ad6d3a 100644
--- a/services/companion/java/com/android/server/companion/transport/Transport.java
+++ b/services/companion/java/com/android/server/companion/transport/Transport.java
@@ -47,7 +47,6 @@
     protected static final boolean DEBUG = Build.IS_DEBUGGABLE;
 
     static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN
-    public static final int MESSAGE_REQUEST_PLATFORM_INFO = 0x63807073; // ?PFI
     public static final int MESSAGE_REQUEST_CONTEXT_SYNC = 0x63678883; // ?CXS
     public static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 0x63826983; // ?RES
 
@@ -113,17 +112,17 @@
     /**
      * Start listening to messages.
      */
-    public abstract void start();
+    abstract void start();
 
     /**
      * Soft stop listening to the incoming data without closing the streams.
      */
-    public abstract void stop();
+    abstract void stop();
 
     /**
      * Stop listening to the incoming data and close the streams.
      */
-    public abstract void close();
+    abstract void close();
 
     protected abstract void sendMessage(int message, int sequence, @NonNull byte[] data)
             throws IOException;
@@ -183,11 +182,6 @@
                 sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, data);
                 break;
             }
-            case MESSAGE_REQUEST_PLATFORM_INFO: {
-                callback(message, data);
-                // DO NOT SEND A RESPONSE!
-                break;
-            }
             case MESSAGE_REQUEST_CONTEXT_SYNC: {
                 callback(message, data);
                 sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, EmptyArray.BYTE);
diff --git a/services/core/java/com/android/server/WallpaperUpdateReceiver.java b/services/core/java/com/android/server/WallpaperUpdateReceiver.java
index 9917892..2812233 100644
--- a/services/core/java/com/android/server/WallpaperUpdateReceiver.java
+++ b/services/core/java/com/android/server/WallpaperUpdateReceiver.java
@@ -88,7 +88,7 @@
         } else {
             //live wallpaper
             ComponentName currCN = info.getComponent();
-            ComponentName defaultCN = WallpaperManager.getDefaultWallpaperComponent(context);
+            ComponentName defaultCN = WallpaperManager.getCmfDefaultWallpaperComponent(context);
             if (!currCN.equals(defaultCN)) {
                 return true;
             }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 8ce1829..a4cd278 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -3761,6 +3761,15 @@
 
     @Override
     public void forceStopPackage(final String packageName, int userId) {
+        forceStopPackage(packageName, userId, /*flags=*/ 0);
+    }
+
+    @Override
+    public void forceStopPackageEvenWhenStopping(final String packageName, int userId) {
+        forceStopPackage(packageName, userId, ActivityManager.FLAG_OR_STOPPED);
+    }
+
+    private void forceStopPackage(final String packageName, int userId, int userRunningFlags) {
         if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)
                 != PackageManager.PERMISSION_GRANTED) {
             String msg = "Permission Denial: forceStopPackage() from pid="
@@ -3776,7 +3785,7 @@
         final long callingId = Binder.clearCallingIdentity();
         try {
             IPackageManager pm = AppGlobals.getPackageManager();
-            synchronized(this) {
+            synchronized (this) {
                 int[] users = userId == UserHandle.USER_ALL
                         ? mUserController.getUsers() : new int[] { userId };
                 for (int user : users) {
@@ -3804,7 +3813,7 @@
                         Slog.w(TAG, "Failed trying to unstop package "
                                 + packageName + ": " + e);
                     }
-                    if (mUserController.isUserRunning(user, 0)) {
+                    if (mUserController.isUserRunning(user, userRunningFlags)) {
                         forceStopPackageLocked(packageName, pkgUid, "from pid " + callingPid);
                         finishForceStopPackageLocked(packageName, pkgUid);
                     }
@@ -7511,7 +7520,31 @@
                     "registerUidObserver");
         }
         mUidObserverController.register(observer, which, cutpoint, callingPackage,
-                Binder.getCallingUid());
+                Binder.getCallingUid(), /*uids*/null);
+    }
+
+    /**
+     * Registers a UidObserver with a uid filter.
+     *
+     * @param observer The UidObserver implementation to register.
+     * @param which    A bitmask of events to observe. See ActivityManager.UID_OBSERVER_*.
+     * @param cutpoint The cutpoint for onUidStateChanged events. When the state crosses this
+     *                 threshold in either direction, onUidStateChanged will be called.
+     * @param callingPackage The name of the calling package.
+     * @param uids     A list of uids to watch. If all uids are to be watched, use
+     *                 registerUidObserver instead.
+     * @throws RemoteException
+     * @return Returns A binder token identifying the UidObserver registration.
+     */
+    @Override
+    public IBinder registerUidObserverForUids(IUidObserver observer, int which, int cutpoint,
+            String callingPackage, int[] uids) {
+        if (!hasUsageStatsPermission(callingPackage)) {
+            enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS,
+                    "registerUidObserver");
+        }
+        return mUidObserverController.register(observer, which, cutpoint, callingPackage,
+                Binder.getCallingUid(), uids);
     }
 
     @Override
@@ -7519,6 +7552,40 @@
         mUidObserverController.unregister(observer);
     }
 
+    /**
+     * Adds a uid to the list of uids that a UidObserver will receive updates about.
+     *
+     * @param observerToken  The binder token identifying the UidObserver registration.
+     * @param callingPackage The name of the calling package.
+     * @param uid            The uid to watch.
+     * @throws RemoteException
+     */
+    @Override
+    public void addUidToObserver(IBinder observerToken, String callingPackage, int uid) {
+        if (!hasUsageStatsPermission(callingPackage)) {
+            enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS,
+                    "registerUidObserver");
+        }
+        mUidObserverController.addUidToObserver(observerToken, uid);
+    }
+
+    /**
+     * Removes a uid from the list of uids that a UidObserver will receive updates about.
+     *
+     * @param observerToken  The binder token identifying the UidObserver registration.
+     * @param callingPackage The name of the calling package.
+     * @param uid            The uid to stop watching.
+     * @throws RemoteException
+     */
+    @Override
+    public void removeUidFromObserver(IBinder observerToken, String callingPackage, int uid) {
+        if (!hasUsageStatsPermission(callingPackage)) {
+            enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS,
+                    "registerUidObserver");
+        }
+        mUidObserverController.removeUidFromObserver(observerToken, uid);
+    }
+
     @Override
     public boolean isUidActive(int uid, String callingPackage) {
         if (!hasUsageStatsPermission(callingPackage)) {
@@ -18613,7 +18680,7 @@
                 int which, int cutpoint, @NonNull String callingPackage) {
             mNetworkPolicyUidObserver = observer;
             mUidObserverController.register(observer, which, cutpoint, callingPackage,
-                    Binder.getCallingUid());
+                    Binder.getCallingUid(), /*uids*/null);
         }
 
         @Override
@@ -18906,6 +18973,13 @@
         pw.flush();
     }
 
+    void waitForBroadcastDispatch(@NonNull PrintWriter pw, @NonNull Intent intent) {
+        enforceCallingPermission(permission.DUMP, "waitForBroadcastDispatch");
+        for (BroadcastQueue queue : mBroadcastQueues) {
+            queue.waitForDispatched(intent, pw);
+        }
+    }
+
     void setIgnoreDeliveryGroupPolicy(@NonNull String broadcastAction) {
         Objects.requireNonNull(broadcastAction);
         enforceCallingPermission(permission.DUMP, "waitForBroadcastBarrier()");
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 17a0d62..8759e3f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -368,6 +368,8 @@
                     return runWaitForBroadcastBarrier(pw);
                 case "wait-for-application-barrier":
                     return runWaitForApplicationBarrier(pw);
+                case "wait-for-broadcast-dispatch":
+                    return runWaitForBroadcastDispatch(pw);
                 case "set-ignore-delivery-group-policy":
                     return runSetIgnoreDeliveryGroupPolicy(pw);
                 case "clear-ignore-delivery-group-policy":
@@ -3472,6 +3474,18 @@
         return 0;
     }
 
+    int runWaitForBroadcastDispatch(PrintWriter pw) throws RemoteException {
+        pw = new PrintWriter(new TeeWriter(LOG_WRITER_INFO, pw));
+        final Intent intent;
+        try {
+            intent = makeIntent(UserHandle.USER_CURRENT);
+        } catch (URISyntaxException e) {
+            throw new RuntimeException(e.getMessage(), e);
+        }
+        mInternal.waitForBroadcastDispatch(pw, intent);
+        return 0;
+    }
+
     int runSetIgnoreDeliveryGroupPolicy(PrintWriter pw) throws RemoteException {
         final String broadcastAction = getNextArgRequired();
         mInternal.setIgnoreDeliveryGroupPolicy(broadcastAction);
diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index 4d46963..87214de 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -179,11 +179,39 @@
      * being "runnable" to give other processes a chance to run.
      */
     public int MAX_RUNNING_ACTIVE_BROADCASTS = DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS;
-    private static final String KEY_MAX_RUNNING_ACTIVE_BROADCASTS = "bcast_max_running_active_broadcasts";
+    private static final String KEY_MAX_RUNNING_ACTIVE_BROADCASTS =
+            "bcast_max_running_active_broadcasts";
     private static final int DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS =
             ActivityManager.isLowRamDeviceStatic() ? 8 : 16;
 
     /**
+     * For {@link BroadcastQueueModernImpl}: Maximum number of active "blocking" broadcasts
+     * to dispatch to a "running" System process queue before we retire them back to
+     * being "runnable" to give other processes a chance to run. Here "blocking" refers to
+     * whether or not we are going to block on the finishReceiver() to be called before moving
+     * to the next broadcast.
+     */
+    public int MAX_CORE_RUNNING_BLOCKING_BROADCASTS = DEFAULT_MAX_CORE_RUNNING_BLOCKING_BROADCASTS;
+    private static final String KEY_CORE_MAX_RUNNING_BLOCKING_BROADCASTS =
+            "bcast_max_core_running_blocking_broadcasts";
+    private static final int DEFAULT_MAX_CORE_RUNNING_BLOCKING_BROADCASTS =
+            ActivityManager.isLowRamDeviceStatic() ? 8 : 16;
+
+    /**
+     * For {@link BroadcastQueueModernImpl}: Maximum number of active non-"blocking" broadcasts
+     * to dispatch to a "running" System process queue before we retire them back to
+     * being "runnable" to give other processes a chance to run. Here "blocking" refers to
+     * whether or not we are going to block on the finishReceiver() to be called before moving
+     * to the next broadcast.
+     */
+    public int MAX_CORE_RUNNING_NON_BLOCKING_BROADCASTS =
+            DEFAULT_MAX_CORE_RUNNING_NON_BLOCKING_BROADCASTS;
+    private static final String KEY_CORE_MAX_RUNNING_NON_BLOCKING_BROADCASTS =
+            "bcast_max_core_running_non_blocking_broadcasts";
+    private static final int DEFAULT_MAX_CORE_RUNNING_NON_BLOCKING_BROADCASTS =
+            ActivityManager.isLowRamDeviceStatic() ? 32 : 64;
+
+    /**
      * For {@link BroadcastQueueModernImpl}: Maximum number of pending
      * broadcasts to hold for a process before we ignore any delays that policy
      * might have applied to that process.
@@ -369,6 +397,12 @@
                     DEFAULT_MAX_CONSECUTIVE_NORMAL_DISPATCHES);
             MAX_RUNNING_ACTIVE_BROADCASTS = getDeviceConfigInt(KEY_MAX_RUNNING_ACTIVE_BROADCASTS,
                     DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS);
+            MAX_CORE_RUNNING_BLOCKING_BROADCASTS = getDeviceConfigInt(
+                    KEY_CORE_MAX_RUNNING_BLOCKING_BROADCASTS,
+                    DEFAULT_MAX_CORE_RUNNING_BLOCKING_BROADCASTS);
+            MAX_CORE_RUNNING_NON_BLOCKING_BROADCASTS = getDeviceConfigInt(
+                    KEY_CORE_MAX_RUNNING_NON_BLOCKING_BROADCASTS,
+                    DEFAULT_MAX_CORE_RUNNING_NON_BLOCKING_BROADCASTS);
             MAX_PENDING_BROADCASTS = getDeviceConfigInt(KEY_MAX_PENDING_BROADCASTS,
                     DEFAULT_MAX_PENDING_BROADCASTS);
             DELAY_NORMAL_MILLIS = getDeviceConfigLong(KEY_DELAY_NORMAL_MILLIS,
@@ -418,6 +452,10 @@
             pw.print(KEY_MODERN_QUEUE_ENABLED, MODERN_QUEUE_ENABLED).println();
             pw.print(KEY_MAX_RUNNING_PROCESS_QUEUES, MAX_RUNNING_PROCESS_QUEUES).println();
             pw.print(KEY_MAX_RUNNING_ACTIVE_BROADCASTS, MAX_RUNNING_ACTIVE_BROADCASTS).println();
+            pw.print(KEY_CORE_MAX_RUNNING_BLOCKING_BROADCASTS,
+                    MAX_CORE_RUNNING_BLOCKING_BROADCASTS).println();
+            pw.print(KEY_CORE_MAX_RUNNING_NON_BLOCKING_BROADCASTS,
+                    MAX_CORE_RUNNING_NON_BLOCKING_BROADCASTS).println();
             pw.print(KEY_MAX_PENDING_BROADCASTS, MAX_PENDING_BROADCASTS).println();
             pw.print(KEY_DELAY_NORMAL_MILLIS,
                     TimeUtils.formatDuration(DELAY_NORMAL_MILLIS)).println();
diff --git a/services/core/java/com/android/server/am/BroadcastDispatcher.java b/services/core/java/com/android/server/am/BroadcastDispatcher.java
index 2adcf2f..8aa3921 100644
--- a/services/core/java/com/android/server/am/BroadcastDispatcher.java
+++ b/services/core/java/com/android/server/am/BroadcastDispatcher.java
@@ -582,6 +582,38 @@
         }
     }
 
+    private static boolean isDispatchedInDeferrals(@NonNull ArrayList<Deferrals> list,
+            @NonNull Intent intent) {
+        for (int i = 0; i < list.size(); i++) {
+            if (!isDispatched(list.get(i).broadcasts, intent)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static boolean isDispatched(@NonNull ArrayList<BroadcastRecord> list,
+            @NonNull Intent intent) {
+        for (int i = 0; i < list.size(); i++) {
+            if (intent.filterEquals(list.get(i).intent)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public boolean isDispatched(@NonNull Intent intent) {
+        synchronized (mLock) {
+            if ((mCurrentBroadcast != null) && intent.filterEquals(mCurrentBroadcast.intent)) {
+                return false;
+            }
+            return isDispatched(mOrderedBroadcasts, intent)
+                    && isDispatched(mAlarmQueue, intent)
+                    && isDispatchedInDeferrals(mDeferredBroadcasts, intent)
+                    && isDispatchedInDeferrals(mAlarmDeferrals, intent);
+        }
+    }
+
     private static int pendingInDeferralsList(ArrayList<Deferrals> list) {
         int pending = 0;
         final int numEntries = list.size();
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 5c68e67..59aab4f 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -143,6 +143,12 @@
     private int mActiveCountSinceIdle;
 
     /**
+     * Count of {@link #mActive} broadcasts with assumed delivery that have been dispatched
+     * since this queue was last idle.
+     */
+    private int mActiveAssumedDeliveryCountSinceIdle;
+
+    /**
      * Flag indicating that the currently active broadcast is being dispatched
      * was scheduled via a cold start.
      */
@@ -182,7 +188,7 @@
     private int mCountInstrumented;
     private int mCountManifest;
 
-    private boolean mPrioritizeEarliest;
+    private int mCountPrioritizeEarliestRequests;
 
     private @UptimeMillisLong long mRunnableAt = Long.MAX_VALUE;
     private @Reason int mRunnableAtReason = REASON_EMPTY;
@@ -499,6 +505,14 @@
         return mActiveCountSinceIdle;
     }
 
+    /**
+     * Count of {@link #mActive} broadcasts with assumed delivery that have been dispatched
+     * since this queue was last idle.
+     */
+    public int getActiveAssumedDeliveryCountSinceIdle() {
+        return mActiveAssumedDeliveryCountSinceIdle;
+    }
+
     public void setActiveViaColdStart(boolean activeViaColdStart) {
         mActiveViaColdStart = activeViaColdStart;
     }
@@ -532,6 +546,8 @@
         mActive = (BroadcastRecord) next.arg1;
         mActiveIndex = next.argi1;
         mActiveCountSinceIdle++;
+        mActiveAssumedDeliveryCountSinceIdle +=
+                (mActive.isAssumedDelivered(mActiveIndex) ? 1 : 0);
         mActiveViaColdStart = false;
         mActiveWasStopped = false;
         next.recycle();
@@ -545,6 +561,7 @@
         mActive = null;
         mActiveIndex = 0;
         mActiveCountSinceIdle = 0;
+        mActiveAssumedDeliveryCountSinceIdle = 0;
         mActiveViaColdStart = false;
         invalidateRunnableAt();
     }
@@ -748,7 +765,7 @@
         final BroadcastRecord nextLPRecord = (BroadcastRecord) nextLPArgs.arg1;
         final int nextLPRecordIndex = nextLPArgs.argi1;
         final BroadcastRecord nextHPRecord = (BroadcastRecord) highPriorityQueue.peekFirst().arg1;
-        final boolean shouldConsiderLPQueue = (mPrioritizeEarliest
+        final boolean shouldConsiderLPQueue = (mCountPrioritizeEarliestRequests > 0
                 || consecutiveHighPriorityCount >= maxHighPriorityDispatchLimit);
         final boolean isLPQueueEligible = shouldConsiderLPQueue
                 && nextLPRecord.enqueueTime <= nextHPRecord.enqueueTime
@@ -761,10 +778,9 @@
     }
 
     /**
-     * When {@code prioritizeEarliest} is set to {@code true}, then earliest enqueued
-     * broadcasts would be prioritized for dispatching, even if there are urgent broadcasts
-     * waiting. This is typically used in case there are callers waiting for "barrier" to be
-     * reached.
+     * Add a request to prioritize dispatching of broadcasts that have been enqueued the earliest,
+     * even if there are urgent broadcasts waiting to be dispatched. This is typically used in
+     * case there are callers waiting for "barrier" to be reached.
      *
      * @return if this operation may have changed internal state, indicating
      *         that the caller is responsible for invoking
@@ -772,12 +788,38 @@
      */
     @CheckResult
     @VisibleForTesting
-    boolean setPrioritizeEarliest(boolean prioritizeEarliest) {
-        if (mPrioritizeEarliest != prioritizeEarliest) {
-            mPrioritizeEarliest = prioritizeEarliest;
+    boolean addPrioritizeEarliestRequest() {
+        if (mCountPrioritizeEarliestRequests == 0) {
+            mCountPrioritizeEarliestRequests++;
             invalidateRunnableAt();
             return true;
         } else {
+            mCountPrioritizeEarliestRequests++;
+            return false;
+        }
+    }
+
+    /**
+     * Remove a request to prioritize dispatching of broadcasts that have been enqueued the
+     * earliest, even if there are urgent broadcasts waiting to be dispatched. This is typically
+     * used in case there are callers waiting for "barrier" to be reached.
+     *
+     * <p> Once there are no more remaining requests, the dispatching order reverts back to normal.
+     *
+     * @return if this operation may have changed internal state, indicating
+     *         that the caller is responsible for invoking
+     *         {@link BroadcastQueueModernImpl#updateRunnableList}
+     */
+    @CheckResult
+    boolean removePrioritizeEarliestRequest() {
+        mCountPrioritizeEarliestRequests--;
+        if (mCountPrioritizeEarliestRequests == 0) {
+            invalidateRunnableAt();
+            return true;
+        } else if (mCountPrioritizeEarliestRequests < 0) {
+            mCountPrioritizeEarliestRequests = 0;
+            return false;
+        } else {
             return false;
         }
     }
@@ -837,7 +879,7 @@
     }
 
     /**
-     * Quickly determine if this queue has broadcasts enqueued before the given
+     * Quickly determine if this queue has non-deferred broadcasts enqueued before the given
      * barrier timestamp that are still waiting to be delivered.
      */
     public boolean isBeyondBarrierLocked(@UptimeMillisLong long barrierTime) {
@@ -859,6 +901,41 @@
                 || isDeferredUntilActive();
     }
 
+    /**
+     * Quickly determine if this queue has non-deferred broadcasts waiting to be dispatched,
+     * that match {@code intent}, as defined by {@link Intent#filterEquals(Intent)}.
+     */
+    public boolean isDispatched(@NonNull Intent intent) {
+        final boolean activeDispatched = (mActive == null)
+                || (!intent.filterEquals(mActive.intent));
+        final boolean dispatched = isDispatchedInQueue(mPending, intent);
+        final boolean urgentDispatched = isDispatchedInQueue(mPendingUrgent, intent);
+        final boolean offloadDispatched = isDispatchedInQueue(mPendingOffload, intent);
+
+        return (activeDispatched && dispatched && urgentDispatched && offloadDispatched)
+                || isDeferredUntilActive();
+    }
+
+    /**
+     * Quickly determine if the {@code queue} has non-deferred broadcasts waiting to be dispatched,
+     * that match {@code intent}, as defined by {@link Intent#filterEquals(Intent)}.
+     */
+    private boolean isDispatchedInQueue(@NonNull ArrayDeque<SomeArgs> queue,
+            @NonNull Intent intent) {
+        final Iterator<SomeArgs> it = queue.iterator();
+        while (it.hasNext()) {
+            final SomeArgs args = it.next();
+            if (args == null) {
+                return true;
+            }
+            final BroadcastRecord record = (BroadcastRecord) args.arg1;
+            if (intent.filterEquals(record.intent)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     public boolean isRunnable() {
         if (mRunnableAtInvalidated) updateRunnableAt();
         return mRunnableAt != Long.MAX_VALUE;
@@ -1309,6 +1386,7 @@
         pw.print(" m:"); pw.print(mCountManifest);
 
         pw.print(" csi:"); pw.print(mActiveCountSinceIdle);
+        pw.print(" adcsi:"); pw.print(mActiveAssumedDeliveryCountSinceIdle);
         pw.print(" ccu:"); pw.print(mActiveCountConsecutiveUrgent);
         pw.print(" ccn:"); pw.print(mActiveCountConsecutiveNormal);
         pw.println();
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 6d1344d..8e76e5b 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -192,7 +192,7 @@
     public abstract boolean isIdleLocked();
 
     /**
-     * Quickly determine if this queue has broadcasts enqueued before the given
+     * Quickly determine if this queue has non-deferred broadcasts enqueued before the given
      * barrier timestamp that are still waiting to be delivered.
      *
      * @see #waitForIdle
@@ -202,6 +202,15 @@
     public abstract boolean isBeyondBarrierLocked(@UptimeMillisLong long barrierTime);
 
     /**
+     * Quickly determine if this queue has non-deferred broadcasts waiting to be dispatched,
+     * that match {@code intent}, as defined by {@link Intent#filterEquals(Intent)}.
+     *
+     * @see #waitForDispatched(Intent, PrintWriter)
+     */
+    @GuardedBy("mService")
+    public abstract boolean isDispatchedLocked(@NonNull Intent intent);
+
+    /**
      * Wait until this queue becomes completely idle.
      * <p>
      * Any broadcasts waiting to be delivered at some point in the future will
@@ -214,7 +223,7 @@
     public abstract void waitForIdle(@NonNull PrintWriter pw);
 
     /**
-     * Wait until any currently waiting broadcasts have been dispatched.
+     * Wait until any currently waiting non-deferred broadcasts have been dispatched.
      * <p>
      * Any broadcasts waiting to be delivered at some point in the future will
      * be dispatched as quickly as possible.
@@ -225,6 +234,15 @@
     public abstract void waitForBarrier(@NonNull PrintWriter pw);
 
     /**
+     * Wait until all non-deferred broadcasts matching {@code intent}, as defined by
+     * {@link Intent#filterEquals(Intent)}, have been dispatched.
+     * <p>
+     * Any broadcasts waiting to be delivered at some point in the future will
+     * be dispatched as quickly as possible.
+     */
+    public abstract void waitForDispatched(@NonNull Intent intent, @NonNull PrintWriter pw);
+
+    /**
      * Delays delivering broadcasts to the specified package.
      *
      * <p> Note that this is only valid for modern queue.
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index 4a69f90..7f3ceb5 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -1793,6 +1793,23 @@
         return mDispatcher.isBeyondBarrier(barrierTime);
     }
 
+    public boolean isDispatchedLocked(Intent intent) {
+        if (isIdleLocked()) return true;
+
+        for (int i = 0; i < mParallelBroadcasts.size(); i++) {
+            if (intent.filterEquals(mParallelBroadcasts.get(i).intent)) {
+                return false;
+            }
+        }
+
+        final BroadcastRecord pending = getPendingBroadcastLocked();
+        if ((pending != null) && intent.filterEquals(pending.intent)) {
+            return false;
+        }
+
+        return mDispatcher.isDispatched(intent);
+    }
+
     public void waitForIdle(PrintWriter pw) {
         waitFor(() -> isIdleLocked(), pw, "idle");
     }
@@ -1802,6 +1819,10 @@
         waitFor(() -> isBeyondBarrierLocked(barrierTime), pw, "barrier");
     }
 
+    public void waitForDispatched(Intent intent, PrintWriter pw) {
+        waitFor(() -> isDispatchedLocked(intent), pw, "dispatch");
+    }
+
     private void waitFor(BooleanSupplier condition, PrintWriter pw, String conditionName) {
         long lastPrint = 0;
         while (true) {
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 96e1523..10a7c12 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -40,6 +40,7 @@
 import static com.android.server.am.BroadcastRecord.getReceiverUid;
 import static com.android.server.am.BroadcastRecord.isDeliveryStateTerminal;
 
+import android.annotation.CheckResult;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UptimeMillisLong;
@@ -446,43 +447,29 @@
 
             if (DEBUG_BROADCAST) logv("Promoting " + queue
                     + " from runnable to running; process is " + queue.app);
-
-            // Allocate this available permit and start running!
-            final int queueIndex = getRunningIndexOf(null);
-            mRunning[queueIndex] = queue;
-            avail--;
-
-            // Remove ourselves from linked list of runnable things
-            mRunnableHead = removeFromRunnableList(mRunnableHead, queue);
-
-            // Emit all trace events for this process into a consistent track
-            queue.runningTraceTrackName = TAG + ".mRunning[" + queueIndex + "]";
-            queue.runningOomAdjusted = queue.isPendingManifest()
-                    || queue.isPendingOrdered()
-                    || queue.isPendingResultTo();
-
-            // If already warm, we can make OOM adjust request immediately;
-            // otherwise we need to wait until process becomes warm
+            promoteToRunningLocked(queue);
+            final boolean completed;
             if (processWarm) {
-                notifyStartedRunning(queue);
                 updateOomAdj |= queue.runningOomAdjusted;
-            }
-
-            // If we're already warm, schedule next pending broadcast now;
-            // otherwise we'll wait for the cold start to circle back around
-            queue.makeActiveNextPending();
-            if (processWarm) {
-                queue.traceProcessRunningBegin();
-                scheduleReceiverWarmLocked(queue);
+                completed = scheduleReceiverWarmLocked(queue);
             } else {
-                queue.traceProcessStartingBegin();
-                scheduleReceiverColdLocked(queue);
+                completed = scheduleReceiverColdLocked(queue);
             }
+            // If we are done with delivering the broadcasts to the process, we can demote it
+            // from the "running" list.
+            if (completed) {
+                demoteFromRunningLocked(queue);
+            }
+            // TODO: If delivering broadcasts to a process is finished, we don't have to hold
+            // a slot for it.
+            avail--;
 
             // Move to considering next runnable queue
             queue = nextQueue;
         }
 
+        // TODO: We need to update oomAdj early as this currently doesn't guarantee that the
+        // procState is updated correctly when the app is handling a broadcast.
         if (updateOomAdj) {
             mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_RECEIVER);
         }
@@ -514,7 +501,9 @@
 
             queue.traceProcessEnd();
             queue.traceProcessRunningBegin();
-            scheduleReceiverWarmLocked(queue);
+            if (scheduleReceiverWarmLocked(queue)) {
+                demoteFromRunningLocked(queue);
+            }
 
             // We might be willing to kick off another cold start
             enqueueUpdateRunningList();
@@ -558,6 +547,7 @@
             if (queue.isActive()) {
                 finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_FAILURE,
                         "onApplicationCleanupLocked");
+                demoteFromRunningLocked(queue);
             }
 
             // Skip any pending registered receivers, since the old process
@@ -695,8 +685,13 @@
      * Schedule the currently active broadcast on the given queue when we know
      * the process is cold. This kicks off a cold start and will eventually call
      * through to {@link #scheduleReceiverWarmLocked} once it's ready.
+     *
+     * @return {@code true} if the broadcast delivery is finished and the process queue can
+     *         be demoted from the running list. Otherwise {@code false}.
      */
-    private void scheduleReceiverColdLocked(@NonNull BroadcastProcessQueue queue) {
+    @CheckResult
+    @GuardedBy("mService")
+    private boolean scheduleReceiverColdLocked(@NonNull BroadcastProcessQueue queue) {
         checkState(queue.isActive(), "isActive");
 
         // Remember that active broadcast was scheduled via a cold start
@@ -711,12 +706,14 @@
             mRunningColdStart = null;
             finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_SKIPPED,
                     "BroadcastFilter for cold app");
-            return;
+            return true;
         }
 
-        if (maybeSkipReceiver(queue, r, index)) {
+        final String skipReason = shouldSkipReceiver(queue, r, index);
+        if (skipReason != null) {
             mRunningColdStart = null;
-            return;
+            finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_SKIPPED, skipReason);
+            return true;
         }
 
         final ApplicationInfo info = ((ResolveInfo) receiver).activityInfo.applicationInfo;
@@ -742,8 +739,9 @@
             mRunningColdStart = null;
             finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_FAILURE,
                     "startProcessLocked failed");
-            return;
+            return true;
         }
+        return false;
     }
 
     /**
@@ -754,38 +752,46 @@
      * results by calling through to {@link #finishReceiverLocked}, both in the
      * case where a broadcast is handled by a remote app, and the case where the
      * broadcast was finished locally without the remote app being involved.
+     *
+     * @return {@code true} if the broadcast delivery is finished and the process queue can
+     *         be demoted from the running list. Otherwise {@code false}.
      */
+    @CheckResult
     @GuardedBy("mService")
-    private void scheduleReceiverWarmLocked(@NonNull BroadcastProcessQueue queue) {
+    private boolean scheduleReceiverWarmLocked(@NonNull BroadcastProcessQueue queue) {
         checkState(queue.isActive(), "isActive");
 
-        final BroadcastRecord r = queue.getActive();
-        final int index = queue.getActiveIndex();
+        final int cookie = traceBegin("scheduleReceiverWarmLocked");
+        while (queue.isActive()) {
+            final BroadcastRecord r = queue.getActive();
+            final int index = queue.getActiveIndex();
 
-        if (r.terminalCount == 0) {
-            r.dispatchTime = SystemClock.uptimeMillis();
-            r.dispatchRealTime = SystemClock.elapsedRealtime();
-            r.dispatchClockTime = System.currentTimeMillis();
-        }
+            if (r.terminalCount == 0) {
+                r.dispatchTime = SystemClock.uptimeMillis();
+                r.dispatchRealTime = SystemClock.elapsedRealtime();
+                r.dispatchClockTime = System.currentTimeMillis();
+            }
 
-        if (maybeSkipReceiver(queue, r, index)) {
-            return;
-        }
-        dispatchReceivers(queue, r, index);
-    }
+            final String skipReason = shouldSkipReceiver(queue, r, index);
+            if (skipReason == null) {
+                final boolean isBlockingDispatch = dispatchReceivers(queue, r, index);
+                if (isBlockingDispatch) {
+                    traceEnd(cookie);
+                    return false;
+                }
+            } else {
+                finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_SKIPPED, skipReason);
+            }
 
-    /**
-     * Examine a receiver and possibly skip it.  The method returns true if the receiver is
-     * skipped (and therefore no more work is required).
-     */
-    private boolean maybeSkipReceiver(@NonNull BroadcastProcessQueue queue,
-            @NonNull BroadcastRecord r, int index) {
-        final String reason = shouldSkipReceiver(queue, r, index);
-        if (reason != null) {
-            finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_SKIPPED, reason);
-            return true;
+            if (shouldRetire(queue)) {
+                break;
+            }
+
+            // We're on a roll; move onto the next broadcast for this process
+            queue.makeActiveNextPending();
         }
-        return false;
+        traceEnd(cookie);
+        return true;
     }
 
     /**
@@ -826,24 +832,21 @@
     }
 
     /**
-     * Return true if this receiver should be assumed to have been delivered.
-     */
-    private boolean isAssumedDelivered(BroadcastRecord r, int index) {
-        return (r.receivers.get(index) instanceof BroadcastFilter) && !r.ordered
-                && (r.resultTo == null);
-    }
-
-    /**
      * A receiver is about to be dispatched.  Start ANR timers, if necessary.
+     *
+     * @return {@code true} if this a blocking delivery. That is, we are going to block on the
+     *         finishReceiver() to be called before moving to the next broadcast. Otherwise,
+     *         {@code false}.
      */
-    private void dispatchReceivers(@NonNull BroadcastProcessQueue queue,
+    @CheckResult
+    private boolean dispatchReceivers(@NonNull BroadcastProcessQueue queue,
             @NonNull BroadcastRecord r, int index) {
         final ProcessRecord app = queue.app;
         final Object receiver = r.receivers.get(index);
 
         // Skip ANR tracking early during boot, when requested, or when we
         // immediately assume delivery success
-        final boolean assumeDelivered = isAssumedDelivered(r, index);
+        final boolean assumeDelivered = r.isAssumedDelivered(index);
         if (mService.mProcessesReady && !r.timeoutExempt && !assumeDelivered) {
             queue.lastCpuDelayTime = queue.app.getCpuDelayTime();
 
@@ -898,6 +901,7 @@
                     if (assumeDelivered) {
                         finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_DELIVERED,
                                 "assuming delivered");
+                        return false;
                     }
                 } else {
                     notifyScheduleReceiver(app, r, (ResolveInfo) receiver);
@@ -908,17 +912,21 @@
                             r.shareIdentity ? r.callingUid : Process.INVALID_UID,
                             r.shareIdentity ? r.callerPackage : null);
                 }
+                return true;
             } catch (RemoteException e) {
                 final String msg = "Failed to schedule " + r + " to " + receiver
                         + " via " + app + ": " + e;
                 logw(msg);
                 app.killLocked("Can't deliver broadcast", ApplicationExitInfo.REASON_OTHER,
                         ApplicationExitInfo.SUBREASON_UNDELIVERED_BROADCAST, true);
-                finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_FAILURE, "remote app");
+                finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_FAILURE,
+                        "remote app");
+                return false;
             }
         } else {
             finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_FAILURE,
                     "missing IApplicationThread");
+            return false;
         }
     }
 
@@ -989,6 +997,7 @@
     private void deliveryTimeoutHardLocked(@NonNull BroadcastProcessQueue queue) {
         finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_TIMEOUT,
                 "deliveryTimeoutHardLocked");
+        demoteFromRunningLocked(queue);
     }
 
     @Override
@@ -1015,7 +1024,7 @@
         // To ensure that "beyond" high-water marks are updated in a monotonic
         // way, we finish this receiver before possibly skipping any remaining
         // aborted receivers
-        final boolean res = finishReceiverActiveLocked(queue,
+        finishReceiverActiveLocked(queue,
                 BroadcastRecord.DELIVERY_DELIVERED, "remote app");
 
         // When the caller aborted an ordered broadcast, we mark all
@@ -1027,30 +1036,52 @@
             }
         }
 
-        return res;
+        if (shouldRetire(queue)) {
+            demoteFromRunningLocked(queue);
+            return true;
+        }
+
+        // We're on a roll; move onto the next broadcast for this process
+        queue.makeActiveNextPending();
+        if (scheduleReceiverWarmLocked(queue)) {
+            demoteFromRunningLocked(queue);
+            return true;
+        }
+
+        return false;
     }
 
     /**
-     * Return true if there are more broadcasts in the queue and the queue is runnable.
+     * Return true if there are no more broadcasts in the queue or if the queue is not runnable.
      */
-    private boolean shouldContinueScheduling(@NonNull BroadcastProcessQueue queue) {
+    private boolean shouldRetire(@NonNull BroadcastProcessQueue queue) {
         // If we've made reasonable progress, periodically retire ourselves to
         // avoid starvation of other processes and stack overflow when a
         // broadcast is immediately finished without waiting
-        final boolean shouldRetire =
-                (queue.getActiveCountSinceIdle() >= mConstants.MAX_RUNNING_ACTIVE_BROADCASTS);
+        final boolean shouldRetire;
+        if (UserHandle.isCore(queue.uid)) {
+            final int nonBlockingDeliveryCount = queue.getActiveAssumedDeliveryCountSinceIdle();
+            final int blockingDeliveryCount = (queue.getActiveCountSinceIdle()
+                    - queue.getActiveAssumedDeliveryCountSinceIdle());
+            shouldRetire = (blockingDeliveryCount
+                    >= mConstants.MAX_CORE_RUNNING_BLOCKING_BROADCASTS) || (nonBlockingDeliveryCount
+                    >= mConstants.MAX_CORE_RUNNING_NON_BLOCKING_BROADCASTS);
+        } else {
+            shouldRetire =
+                    (queue.getActiveCountSinceIdle() >= mConstants.MAX_RUNNING_ACTIVE_BROADCASTS);
+        }
 
-        return queue.isRunnable() && queue.isProcessWarm() && !shouldRetire;
+        return !queue.isRunnable() || !queue.isProcessWarm() || shouldRetire;
     }
 
     /**
      * Terminate all active broadcasts on the queue.
      */
-    private boolean finishReceiverActiveLocked(@NonNull BroadcastProcessQueue queue,
+    private void finishReceiverActiveLocked(@NonNull BroadcastProcessQueue queue,
             @DeliveryState int deliveryState, @NonNull String reason) {
         if (!queue.isActive()) {
             logw("Ignoring finish; no active broadcast for " + queue);
-            return false;
+            return;
         }
 
         final int cookie = traceBegin("finishReceiver");
@@ -1077,27 +1108,63 @@
         // Given that a receiver just finished, check if the "waitingFor" conditions are met.
         checkAndRemoveWaitingFor();
 
-        final boolean res = shouldContinueScheduling(queue);
-        if (res) {
-            // We're on a roll; move onto the next broadcast for this process
-            queue.makeActiveNextPending();
-            scheduleReceiverWarmLocked(queue);
-        } else {
-            // We've drained running broadcasts; maybe move back to runnable
-            queue.makeActiveIdle();
-            queue.traceProcessEnd();
-
-            final int queueIndex = getRunningIndexOf(queue);
-            mRunning[queueIndex] = null;
-            updateRunnableList(queue);
-            enqueueUpdateRunningList();
-
-            // Tell other OS components that app is not actively running, giving
-            // a chance to update OOM adjustment
-            notifyStoppedRunning(queue);
-        }
         traceEnd(cookie);
-        return res;
+    }
+
+    /**
+     * Promote a process to the "running" list.
+     */
+    @GuardedBy("mService")
+    private void promoteToRunningLocked(@NonNull BroadcastProcessQueue queue) {
+        // Allocate this available permit and start running!
+        final int queueIndex = getRunningIndexOf(null);
+        mRunning[queueIndex] = queue;
+
+        // Remove ourselves from linked list of runnable things
+        mRunnableHead = removeFromRunnableList(mRunnableHead, queue);
+
+        // Emit all trace events for this process into a consistent track
+        queue.runningTraceTrackName = TAG + ".mRunning[" + queueIndex + "]";
+        queue.runningOomAdjusted = queue.isPendingManifest()
+                || queue.isPendingOrdered()
+                || queue.isPendingResultTo();
+
+        // If already warm, we can make OOM adjust request immediately;
+        // otherwise we need to wait until process becomes warm
+        final boolean processWarm = queue.isProcessWarm();
+        if (processWarm) {
+            notifyStartedRunning(queue);
+        }
+
+        // If we're already warm, schedule next pending broadcast now;
+        // otherwise we'll wait for the cold start to circle back around
+        queue.makeActiveNextPending();
+        if (processWarm) {
+            queue.traceProcessRunningBegin();
+        } else {
+            queue.traceProcessStartingBegin();
+        }
+    }
+
+    /**
+     * Demote a process from the "running" list.
+     */
+    @GuardedBy("mService")
+    private void demoteFromRunningLocked(@NonNull BroadcastProcessQueue queue) {
+        final int cookie = traceBegin("demoteFromRunning");
+        // We've drained running broadcasts; maybe move back to runnable
+        queue.makeActiveIdle();
+        queue.traceProcessEnd();
+
+        final int queueIndex = getRunningIndexOf(queue);
+        mRunning[queueIndex] = null;
+        updateRunnableList(queue);
+        enqueueUpdateRunningList();
+
+        // Tell other OS components that app is not actively running, giving
+        // a chance to update OOM adjustment
+        notifyStoppedRunning(queue);
+        traceEnd(cookie);
     }
 
     /**
@@ -1376,6 +1443,16 @@
     }
 
     @Override
+    public boolean isDispatchedLocked(@NonNull Intent intent) {
+        return isDispatchedLocked(intent, LOG_WRITER_INFO);
+    }
+
+    public boolean isDispatchedLocked(@NonNull Intent intent, @NonNull PrintWriter pw) {
+        return testAllProcessQueues(q -> q.isDispatched(intent),
+                "dispatch of " + intent, pw);
+    }
+
+    @Override
     public void waitForIdle(@NonNull PrintWriter pw) {
         waitFor(() -> isIdleLocked(pw));
     }
@@ -1383,28 +1460,35 @@
     @Override
     public void waitForBarrier(@NonNull PrintWriter pw) {
         final long now = SystemClock.uptimeMillis();
-        waitFor(() -> isBeyondBarrierLocked(now, pw));
+        synchronized (mService) {
+            forEachMatchingQueue(QUEUE_PREDICATE_ANY,
+                    q -> q.addPrioritizeEarliestRequest());
+        }
+        try {
+            waitFor(() -> isBeyondBarrierLocked(now, pw));
+        } finally {
+            synchronized (mService) {
+                forEachMatchingQueue(QUEUE_PREDICATE_ANY,
+                        q -> q.removePrioritizeEarliestRequest());
+            }
+        }
+    }
+
+    @Override
+    public void waitForDispatched(@NonNull Intent intent, @NonNull PrintWriter pw) {
+        waitFor(() -> isDispatchedLocked(intent, pw));
     }
 
     private void waitFor(@NonNull BooleanSupplier condition) {
         final CountDownLatch latch = new CountDownLatch(1);
         synchronized (mService) {
             mWaitingFor.add(Pair.create(condition, latch));
-            forEachMatchingQueue(QUEUE_PREDICATE_ANY,
-                    (q) -> q.setPrioritizeEarliest(true));
         }
         enqueueUpdateRunningList();
         try {
             latch.await();
         } catch (InterruptedException e) {
             throw new RuntimeException(e);
-        } finally {
-            synchronized (mService) {
-                if (mWaitingFor.isEmpty()) {
-                    forEachMatchingQueue(QUEUE_PREDICATE_ANY,
-                            (q) -> q.setPrioritizeEarliest(false));
-                }
-            }
         }
     }
 
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 64fe393..a9274408 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -237,6 +237,14 @@
         }
     }
 
+    /**
+     * Return true if this receiver should be assumed to have been delivered.
+     */
+    boolean isAssumedDelivered(int index) {
+        return (receivers.get(index) instanceof BroadcastFilter) && !ordered
+                && (resultTo == null);
+    }
+
     ProcessRecord curApp;       // hosting application of current receiver.
     ComponentName curComponent; // the receiver class that is currently running.
     ActivityInfo curReceiver;   // the manifest receiver that is currently running.
diff --git a/services/core/java/com/android/server/am/UidObserverController.java b/services/core/java/com/android/server/am/UidObserverController.java
index 790cc7b..a258208 100644
--- a/services/core/java/com/android/server/am/UidObserverController.java
+++ b/services/core/java/com/android/server/am/UidObserverController.java
@@ -27,7 +27,9 @@
 import android.app.ActivityManagerProto;
 import android.app.IUidObserver;
 import android.content.pm.PackageManager;
+import android.os.Binder;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -43,6 +45,8 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.UUID;
 
 public class UidObserverController {
     /** If a UID observer takes more than this long, send a WTF. */
@@ -79,14 +83,19 @@
         mValidateUids = new ActiveUids(null /* service */, false /* postChangesToAtm */);
     }
 
-    void register(@NonNull IUidObserver observer, int which, int cutpoint,
-            @NonNull String callingPackage, int callingUid) {
+    IBinder register(@NonNull IUidObserver observer, int which, int cutpoint,
+            @NonNull String callingPackage, int callingUid, @Nullable int[] uids) {
+        IBinder token = new Binder("UidObserver-" + callingPackage + "-"
+                + UUID.randomUUID().toString());
+
         synchronized (mLock) {
             mUidObservers.register(observer, new UidObserverRegistration(callingUid,
                     callingPackage, which, cutpoint,
                     ActivityManager.checkUidPermission(INTERACT_ACROSS_USERS_FULL, callingUid)
-                    == PackageManager.PERMISSION_GRANTED));
+                    == PackageManager.PERMISSION_GRANTED, uids, token));
         }
+
+        return token;
     }
 
     void unregister(@NonNull IUidObserver observer) {
@@ -95,6 +104,42 @@
         }
     }
 
+    void addUidToObserver(@NonNull IBinder observerToken, int uid) {
+        synchronized (mLock) {
+            int i = mUidObservers.beginBroadcast();
+            while (i-- > 0) {
+                var reg = (UidObserverRegistration) mUidObservers.getBroadcastCookie(i);
+                if (reg.getToken().equals(observerToken)) {
+                    reg.addUid(uid);
+                    break;
+                }
+
+                if (i == 0) {
+                    Slog.e(TAG_UID_OBSERVERS, "Unable to find UidObserver by token");
+                }
+            }
+            mUidObservers.finishBroadcast();
+        }
+    }
+
+    void removeUidFromObserver(@NonNull IBinder observerToken, int uid) {
+        synchronized (mLock) {
+            int i = mUidObservers.beginBroadcast();
+            while (i-- > 0) {
+                var reg = (UidObserverRegistration) mUidObservers.getBroadcastCookie(i);
+                if (reg.getToken().equals(observerToken)) {
+                    reg.removeUid(uid);
+                    break;
+                }
+
+                if (i == 0) {
+                    Slog.e(TAG_UID_OBSERVERS, "Unable to find UidObserver by token");
+                }
+            }
+            mUidObservers.finishBroadcast();
+        }
+    }
+
     int enqueueUidChange(@Nullable ChangeRecord currentRecord, int uid, int change, int procState,
             int procAdj, long procStateSeq, int capability, boolean ephemeral) {
         synchronized (mLock) {
@@ -257,6 +302,10 @@
                 final ChangeRecord item = mActiveUidChanges[j];
                 final long start = SystemClock.uptimeMillis();
                 final int change = item.change;
+                // Is the observer watching this uid?
+                if (!reg.isWatchingUid(item.uid)) {
+                    continue;
+                }
                 // Does the user have permission? Don't send a non user UID change otherwise
                 if (UserHandle.getUserId(item.uid) != UserHandle.getUserId(reg.mUid)
                         && !reg.mCanInteractAcrossUsers) {
@@ -450,6 +499,8 @@
         private final int mWhich;
         private final int mCutpoint;
         private final boolean mCanInteractAcrossUsers;
+        private final IBinder mToken;
+        private int[] mUids;
 
         /**
          * Total # of callback calls that took more than {@link #SLOW_UID_OBSERVER_THRESHOLD_MS}.
@@ -481,16 +532,94 @@
         };
 
         UidObserverRegistration(int uid, @NonNull String pkg, int which, int cutpoint,
-                boolean canInteractAcrossUsers) {
+                boolean canInteractAcrossUsers, @Nullable int[] uids, @NonNull IBinder token) {
             this.mUid = uid;
             this.mPkg = pkg;
             this.mWhich = which;
             this.mCutpoint = cutpoint;
             this.mCanInteractAcrossUsers = canInteractAcrossUsers;
+
+            if (uids != null) {
+                this.mUids = uids.clone();
+                Arrays.sort(this.mUids);
+            } else {
+                this.mUids = null;
+            }
+
+            this.mToken = token;
+
             mLastProcStates = cutpoint >= ActivityManager.MIN_PROCESS_STATE
                     ? new SparseIntArray() : null;
         }
 
+        boolean isWatchingUid(int uid) {
+            if (mUids == null) {
+                return true;
+            }
+
+            return Arrays.binarySearch(mUids, uid) >= 0;
+        }
+
+        void addUid(int uid) {
+            if (mUids == null) {
+                return;
+            }
+
+            int[] temp = mUids;
+            mUids = new int[temp.length + 1];
+            boolean inserted = false;
+            for (int i = 0; i < temp.length; i++) {
+                if (!inserted) {
+                    if (temp[i] < uid) {
+                        mUids[i] = temp[i];
+                    } else if (temp[i] == uid) {
+                        // Duplicate uid, no-op and fallback to the previous array
+                        mUids = temp;
+                        return;
+                    } else {
+                        mUids[i] = uid;
+                        mUids[i + 1] = temp[i];
+                        inserted = true;
+                    }
+                } else {
+                    mUids[i + 1] = temp[i];
+                }
+            }
+
+            if (!inserted) {
+                mUids[temp.length] = uid;
+            }
+        }
+
+        void removeUid(int uid) {
+            if (mUids == null || mUids.length == 0) {
+                return;
+            }
+
+            int[] temp = mUids;
+            mUids = new int[temp.length - 1];
+            boolean removed = false;
+            for (int i = 0; i < temp.length; i++) {
+                if (!removed) {
+                    if (temp[i] == uid) {
+                        removed = true;
+                    } else if (i == temp.length - 1) {
+                        // Uid not found, no-op and fallback to the previous array
+                        mUids = temp;
+                        return;
+                    } else {
+                        mUids[i] = temp[i];
+                    }
+                } else {
+                    mUids[i - 1] = temp[i];
+                }
+            }
+        }
+
+        IBinder getToken() {
+            return mToken;
+        }
+
         void dump(@NonNull PrintWriter pw, @NonNull IUidObserver observer) {
             pw.print("    ");
             UserHandle.formatUid(pw, mUid);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 2039325..3140387 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -624,7 +624,7 @@
     private int mZenModeAffectedStreams = 0;
 
     // Streams currently muted by ringer mode and dnd
-    private int mRingerAndZenModeMutedStreams;
+    protected static volatile int sRingerAndZenModeMutedStreams;
 
     /** Streams that can be muted. Do not resolve to aliases when checking.
      * @see System#MUTE_STREAMS_AFFECTED */
@@ -1320,7 +1320,9 @@
 
         // Call setRingerModeInt() to apply correct mute
         // state on streams affected by ringer mode.
-        mRingerAndZenModeMutedStreams = 0;
+        sRingerAndZenModeMutedStreams = 0;
+        sMuteLogger.enqueue(new AudioServiceEvents.RingerZenMutedStreamsEvent(
+                sRingerAndZenModeMutedStreams, "onInitStreamsAndVolumes"));
         setRingerModeInt(getRingerModeInternal(), false);
 
         final float[] preScale = new float[3];
@@ -2132,7 +2134,7 @@
 
                 // Unmute streams if required and device is full volume
                 if (isStreamMute(streamType) && mFullVolumeDevices.contains(device)) {
-                    mStreamStates[streamType].mute(false);
+                    mStreamStates[streamType].mute(false, "updateVolumeStates(" + caller);
                 }
             }
         }
@@ -3681,7 +3683,7 @@
                     if (!(mCameraSoundForced
                             && (vss.getStreamType()
                                     == AudioSystem.STREAM_SYSTEM_ENFORCED))) {
-                        boolean changed = vss.mute(state, /* apply= */ false);
+                        boolean changed = vss.mute(state, /* apply= */ false, "muteAliasStreams");
                         if (changed) {
                             streamsToMute.add(stream);
                         }
@@ -3708,7 +3710,8 @@
         boolean wasMuted;
         synchronized (VolumeStreamState.class) {
             final VolumeStreamState streamState = mStreamStates[stream];
-            wasMuted = streamState.mute(false); // if unmuting causes a change, it was muted
+            // if unmuting causes a change, it was muted
+            wasMuted = streamState.mute(false, "onUnmuteStream");
 
             final int device = getDeviceForStream(stream);
             final int index = streamState.getIndex(device);
@@ -3801,13 +3804,13 @@
     /*package*/ void onSetStreamVolume(int streamType, int index, int flags, int device,
             String caller, boolean hasModifyAudioSettings, boolean canChangeMute) {
         final int stream = mStreamVolumeAlias[streamType];
-        setStreamVolumeInt(stream, index, device, false, caller, hasModifyAudioSettings);
         // setting volume on ui sounds stream type also controls silent mode
         if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) ||
                 (stream == getUiSoundsStreamType())) {
             setRingerMode(getNewRingerMode(stream, index, flags),
                     TAG + ".onSetStreamVolume", false /*external*/);
         }
+        setStreamVolumeInt(stream, index, device, false, caller, hasModifyAudioSettings);
         // setting non-zero volume for a muted stream unmutes the stream and vice versa
         // except for BT SCO stream where only explicit mute is allowed to comply to BT requirements
         if ((streamType != AudioSystem.STREAM_BLUETOOTH_SCO) && canChangeMute) {
@@ -5498,12 +5501,16 @@
                               PERSIST_DELAY);
                     }
                 }
-                mStreamStates[streamType].mute(false);
-                mRingerAndZenModeMutedStreams &= ~(1 << streamType);
+                sRingerAndZenModeMutedStreams &= ~(1 << streamType);
+                sMuteLogger.enqueue(new AudioServiceEvents.RingerZenMutedStreamsEvent(
+                        sRingerAndZenModeMutedStreams, "muteRingerModeStreams"));
+                mStreamStates[streamType].mute(false, "muteRingerModeStreams");
             } else {
                 // mute
-                mStreamStates[streamType].mute(true);
-                mRingerAndZenModeMutedStreams |= (1 << streamType);
+                sRingerAndZenModeMutedStreams |= (1 << streamType);
+                sMuteLogger.enqueue(new AudioServiceEvents.RingerZenMutedStreamsEvent(
+                        sRingerAndZenModeMutedStreams, "muteRingerModeStreams"));
+                mStreamStates[streamType].mute(true, "muteRingerModeStreams");
             }
         }
     }
@@ -6702,7 +6709,7 @@
     }
 
     private boolean isStreamMutedByRingerOrZenMode(int streamType) {
-        return (mRingerAndZenModeMutedStreams & (1 << streamType)) != 0;
+        return (sRingerAndZenModeMutedStreams & (1 << streamType)) != 0;
     }
 
     /**
@@ -7613,7 +7620,7 @@
                 Log.i(TAG, String.format("onAccessoryPlugMediaUnmute unmuting device=%d [%s]",
                         newDevice, AudioSystem.getOutputDeviceName(newDevice)));
             }
-            mStreamStates[AudioSystem.STREAM_MUSIC].mute(false);
+            mStreamStates[AudioSystem.STREAM_MUSIC].mute(false, "onAccessoryPlugMediaUnmute");
         }
     }
 
@@ -7989,7 +7996,8 @@
                                                 true /*hasModifyAudioSettings*/);
                                     }
                                     if ((isMuted() != streamMuted) && isVssMuteBijective(stream)) {
-                                        mStreamStates[stream].mute(isMuted());
+                                        mStreamStates[stream].mute(isMuted(),
+                                                "VGS.applyAllVolumes#1");
                                     }
                                 }
                             }
@@ -8030,7 +8038,7 @@
                                     true /*hasModifyAudioSettings*/);
                         }
                         if ((isMuted() != streamMuted) && isVssMuteBijective(stream)) {
-                            mStreamStates[stream].mute(isMuted());
+                            mStreamStates[stream].mute(isMuted(), "VGS.applyAllVolumes#2");
                         }
                     }
                 }
@@ -8718,10 +8726,10 @@
          * @param state the new mute state
          * @return true if the mute state was changed
          */
-        public boolean mute(boolean state) {
+        public boolean mute(boolean state, String source) {
             boolean changed = false;
             synchronized (VolumeStreamState.class) {
-                changed = mute(state, true);
+                changed = mute(state, true, source);
             }
             if (changed) {
                 broadcastMuteSetting(mStreamType, state);
@@ -8770,10 +8778,21 @@
          * It prevents unnecessary calls to {@see AudioSystem#setStreamVolume}
          * @return true if the mute state was changed
          */
-        public boolean mute(boolean state, boolean apply) {
+        public boolean mute(boolean state, boolean apply, String src) {
             synchronized (VolumeStreamState.class) {
                 boolean changed = state != mIsMuted;
                 if (changed) {
+                    sMuteLogger.enqueue(
+                            new AudioServiceEvents.StreamMuteEvent(mStreamType, state, src));
+                    // check to see if unmuting should not have happened due to ringer muted streams
+                    if (!state && isStreamMutedByRingerOrZenMode(mStreamType)) {
+                        Log.e(TAG, "Unmuting stream " + mStreamType
+                                + " despite ringer-zen muted stream 0x"
+                                + Integer.toHexString(AudioService.sRingerAndZenModeMutedStreams),
+                                new Exception()); // this will put a stack trace in the logs
+                        sMuteLogger.enqueue(new AudioServiceEvents.StreamUnmuteErrorEvent(
+                                mStreamType, AudioService.sRingerAndZenModeMutedStreams));
+                    }
                     mIsMuted = state;
                     if (apply) {
                         doMute();
@@ -9378,9 +9397,9 @@
         public void onChange(boolean selfChange) {
             super.onChange(selfChange);
             // FIXME This synchronized is not necessary if mSettingsLock only protects mRingerMode.
-            //       However there appear to be some missing locks around mRingerAndZenModeMutedStreams
+            //       However there appear to be some missing locks around sRingerAndZenModeMutedStreams
             //       and mRingerModeAffectedStreams, so will leave this synchronized for now.
-            //       mRingerAndZenModeMutedStreams and mMuteAffectedStreams are safe (only accessed once).
+            //       sRingerAndZenModeMutedStreams and mMuteAffectedStreams are safe (only accessed once).
             synchronized (mSettingsLock) {
                 if (updateRingerAndZenModeAffectedStreams()) {
                     /*
@@ -10873,6 +10892,9 @@
             sLifecycleLogger = new EventLogger(LOG_NB_EVENTS_LIFECYCLE,
             "audio services lifecycle");
 
+    static final EventLogger sMuteLogger = new EventLogger(30,
+            "mute commands");
+
     final private EventLogger
             mModeLogger = new EventLogger(LOG_NB_EVENTS_PHONE_STATE,
             "phone state (logged after successful call to AudioSystem.setPhoneState(int, int))");
@@ -10913,7 +10935,7 @@
         pw.println("- mode (external) = " + RINGER_MODE_NAMES[mRingerModeExternal]);
         pw.println("- zen mode:" + Settings.Global.zenModeToString(mNm.getZenMode()));
         dumpRingerModeStreams(pw, "affected", mRingerModeAffectedStreams);
-        dumpRingerModeStreams(pw, "muted", mRingerAndZenModeMutedStreams);
+        dumpRingerModeStreams(pw, "muted", sRingerAndZenModeMutedStreams);
         pw.print("- delegate = "); pw.println(mRingerModeDelegate);
     }
 
@@ -11039,6 +11061,8 @@
         pw.println("\n");
         sVolumeLogger.dump(pw);
         pw.println("\n");
+        sMuteLogger.dump(pw);
+        pw.println("\n");
         dumpSupportedSystemUsage(pw);
 
         pw.println("\n");
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index b022b5b..6ad9390 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -563,4 +563,75 @@
             return new StringBuilder("FIXME invalid event type:").append(mEventType).toString();
         }
     }
+
+    /**
+     * Class to log stream type mute/unmute events
+     */
+    static final class StreamMuteEvent extends EventLogger.Event {
+        final int mStreamType;
+        final boolean mMuted;
+        final String mSource;
+
+        StreamMuteEvent(int streamType, boolean muted, String source) {
+            mStreamType = streamType;
+            mMuted = muted;
+            mSource = source;
+        }
+
+        @Override
+        public String eventToString() {
+            final String streamName =
+                    (mStreamType <= AudioSystem.getNumStreamTypes() && mStreamType >= 0)
+                    ? AudioSystem.STREAM_NAMES[mStreamType]
+                    : ("stream " + mStreamType);
+            return new StringBuilder(streamName)
+                    .append(mMuted ? " muting by " : " unmuting by ")
+                    .append(mSource)
+                    .toString();
+        }
+    }
+
+    /**
+     * Class to log unmute errors that contradict the ringer/zen mode muted streams
+     */
+    static final class StreamUnmuteErrorEvent extends EventLogger.Event {
+        final int mStreamType;
+        final int mRingerZenMutedStreams;
+
+        StreamUnmuteErrorEvent(int streamType, int ringerZenMutedStreams) {
+            mStreamType = streamType;
+            mRingerZenMutedStreams = ringerZenMutedStreams;
+        }
+
+        @Override
+        public String eventToString() {
+            final String streamName =
+                    (mStreamType <= AudioSystem.getNumStreamTypes() && mStreamType >= 0)
+                            ? AudioSystem.STREAM_NAMES[mStreamType]
+                            : ("stream " + mStreamType);
+            return new StringBuilder("Error trying to unmute ")
+                    .append(streamName)
+                    .append(" despite muted streams 0x")
+                    .append(Integer.toHexString(mRingerZenMutedStreams))
+                    .toString();
+        }
+    }
+
+    static final class RingerZenMutedStreamsEvent extends EventLogger.Event {
+        final int mRingerZenMutedStreams;
+        final String mSource;
+
+        RingerZenMutedStreamsEvent(int ringerZenMutedStreams, String source) {
+            mRingerZenMutedStreams = ringerZenMutedStreams;
+            mSource = source;
+        }
+
+        @Override
+        public String eventToString() {
+            return new StringBuilder("RingerZenMutedStreams 0x")
+                    .append(Integer.toHexString(mRingerZenMutedStreams))
+                    .append(" from ").append(mSource)
+                    .toString();
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/input/FocusEventDebugView.java b/services/core/java/com/android/server/input/FocusEventDebugView.java
new file mode 100644
index 0000000..fba2aa6
--- /dev/null
+++ b/services/core/java/com/android/server/input/FocusEventDebugView.java
@@ -0,0 +1,343 @@
+/*
+ * 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.server.input;
+
+import static android.util.TypedValue.COMPLEX_UNIT_DIP;
+import static android.util.TypedValue.COMPLEX_UNIT_SP;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import android.animation.LayoutTransition;
+import android.annotation.AnyThread;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Typeface;
+import android.util.Pair;
+import android.util.Slog;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.InputEvent;
+import android.view.KeyEvent;
+import android.view.RoundedCorner;
+import android.view.View;
+import android.view.WindowInsets;
+import android.view.animation.AccelerateInterpolator;
+import android.widget.HorizontalScrollView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.R;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ *  Displays focus events, such as physical keyboard KeyEvents and non-pointer MotionEvents on
+ *  the screen.
+ */
+class FocusEventDebugView extends LinearLayout {
+
+    private static final String TAG = FocusEventDebugView.class.getSimpleName();
+
+    private static final int KEY_FADEOUT_DURATION_MILLIS = 1000;
+    private static final int KEY_TRANSITION_DURATION_MILLIS = 100;
+
+    private static final int OUTER_PADDING_DP = 16;
+    private static final int KEY_SEPARATION_MARGIN_DP = 16;
+    private static final int KEY_VIEW_SIDE_PADDING_DP = 16;
+    private static final int KEY_VIEW_VERTICAL_PADDING_DP = 8;
+    private static final int KEY_VIEW_MIN_WIDTH_DP = 32;
+    private static final int KEY_VIEW_TEXT_SIZE_SP = 12;
+
+    private final int mOuterPadding;
+
+    // Tracks all keys that are currently pressed/down.
+    private final Map<Pair<Integer /*deviceId*/, Integer /*scanCode*/>, PressedKeyView>
+            mPressedKeys = new HashMap<>();
+
+    private final PressedKeyContainer mPressedKeyContainer;
+    private final PressedKeyContainer mPressedModifierContainer;
+
+    FocusEventDebugView(Context c) {
+        super(c);
+        setFocusableInTouchMode(true);
+
+        final var dm = mContext.getResources().getDisplayMetrics();
+        mOuterPadding = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, OUTER_PADDING_DP, dm);
+
+        setOrientation(HORIZONTAL);
+        setLayoutDirection(LAYOUT_DIRECTION_RTL);
+        setGravity(Gravity.START | Gravity.BOTTOM);
+
+        mPressedKeyContainer = new PressedKeyContainer(mContext);
+        mPressedKeyContainer.setOrientation(HORIZONTAL);
+        mPressedKeyContainer.setGravity(Gravity.RIGHT | Gravity.BOTTOM);
+        mPressedKeyContainer.setLayoutDirection(LAYOUT_DIRECTION_LTR);
+        final var scroller = new HorizontalScrollView(mContext);
+        scroller.addView(mPressedKeyContainer);
+        scroller.setHorizontalScrollBarEnabled(false);
+        scroller.addOnLayoutChangeListener(
+                (view, l, t, r, b, ol, ot, or, ob) -> scroller.fullScroll(View.FOCUS_RIGHT));
+        scroller.setHorizontalFadingEdgeEnabled(true);
+        addView(scroller, new LayoutParams(0, WRAP_CONTENT, 1));
+
+        mPressedModifierContainer = new PressedKeyContainer(mContext);
+        mPressedModifierContainer.setOrientation(VERTICAL);
+        mPressedModifierContainer.setGravity(Gravity.LEFT | Gravity.BOTTOM);
+        addView(mPressedModifierContainer, new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+    }
+
+    @Override
+    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+        int paddingBottom = 0;
+
+        final RoundedCorner bottomLeft =
+                insets.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
+        if (bottomLeft != null) {
+            paddingBottom = bottomLeft.getRadius();
+        }
+
+        final RoundedCorner bottomRight =
+                insets.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT);
+        if (bottomRight != null) {
+            paddingBottom = Math.max(paddingBottom, bottomRight.getRadius());
+        }
+
+        if (insets.getDisplayCutout() != null) {
+            paddingBottom =
+                    Math.max(paddingBottom, insets.getDisplayCutout().getSafeInsetBottom());
+        }
+
+        setPadding(mOuterPadding, mOuterPadding, mOuterPadding, mOuterPadding + paddingBottom);
+        setClipToPadding(false);
+        invalidate();
+        return super.onApplyWindowInsets(insets);
+    }
+
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        handleKeyEvent(event);
+        return super.dispatchKeyEvent(event);
+    }
+
+    /** Report an input event to the debug view. */
+    @AnyThread
+    public void reportEvent(InputEvent event) {
+        if (!(event instanceof KeyEvent)) {
+            // TODO: Support non-pointer MotionEvents.
+            return;
+        }
+        post(() -> handleKeyEvent(KeyEvent.obtain((KeyEvent) event)));
+    }
+
+    private void handleKeyEvent(KeyEvent keyEvent) {
+        final var identifier = new Pair<>(keyEvent.getDeviceId(), keyEvent.getScanCode());
+        final var container = KeyEvent.isModifierKey(keyEvent.getKeyCode())
+                ? mPressedModifierContainer
+                : mPressedKeyContainer;
+        PressedKeyView pressedKeyView = mPressedKeys.get(identifier);
+        switch (keyEvent.getAction()) {
+            case KeyEvent.ACTION_DOWN: {
+                if (pressedKeyView != null) {
+                    if (keyEvent.getRepeatCount() == 0) {
+                        Slog.w(TAG, "Got key down for "
+                                + KeyEvent.keyCodeToString(keyEvent.getKeyCode())
+                                + " that was already tracked as being down.");
+                        break;
+                    }
+                    container.handleKeyRepeat(pressedKeyView);
+                    break;
+                }
+
+                pressedKeyView = new PressedKeyView(mContext, getLabel(keyEvent));
+                mPressedKeys.put(identifier, pressedKeyView);
+                container.handleKeyPressed(pressedKeyView);
+                break;
+            }
+            case KeyEvent.ACTION_UP: {
+                if (pressedKeyView == null) {
+                    Slog.w(TAG, "Got key up for " + KeyEvent.keyCodeToString(keyEvent.getKeyCode())
+                            + " that was not tracked as being down.");
+                    break;
+                }
+                mPressedKeys.remove(identifier);
+                container.handleKeyRelease(pressedKeyView);
+                break;
+            }
+            default:
+                break;
+        }
+        keyEvent.recycle();
+    }
+
+    private static String getLabel(KeyEvent event) {
+        switch (event.getKeyCode()) {
+            case KeyEvent.KEYCODE_SPACE:
+                return "\u2423";
+            case KeyEvent.KEYCODE_TAB:
+                return "\u21e5";
+            case KeyEvent.KEYCODE_ENTER:
+            case KeyEvent.KEYCODE_NUMPAD_ENTER:
+                return "\u23CE";
+            case KeyEvent.KEYCODE_DEL:
+                return "\u232B";
+            case KeyEvent.KEYCODE_FORWARD_DEL:
+                return "\u2326";
+            case KeyEvent.KEYCODE_ESCAPE:
+                return "ESC";
+            case KeyEvent.KEYCODE_DPAD_UP:
+                return "\u2191";
+            case KeyEvent.KEYCODE_DPAD_DOWN:
+                return "\u2193";
+            case KeyEvent.KEYCODE_DPAD_LEFT:
+                return "\u2190";
+            case KeyEvent.KEYCODE_DPAD_RIGHT:
+                return "\u2192";
+            case KeyEvent.KEYCODE_DPAD_UP_RIGHT:
+                return "\u2197";
+            case KeyEvent.KEYCODE_DPAD_UP_LEFT:
+                return "\u2196";
+            case KeyEvent.KEYCODE_DPAD_DOWN_RIGHT:
+                return "\u2198";
+            case KeyEvent.KEYCODE_DPAD_DOWN_LEFT:
+                return "\u2199";
+            default:
+                break;
+        }
+
+        final int unicodeChar = event.getUnicodeChar();
+        if (unicodeChar != 0) {
+            return new String(Character.toChars(unicodeChar));
+        }
+
+        final var label = KeyEvent.keyCodeToString(event.getKeyCode());
+        if (label.startsWith("KEYCODE_")) {
+            return label.substring(8);
+        }
+        return label;
+    }
+
+    private static class PressedKeyView extends TextView {
+
+        private static final ColorFilter sInvertColors = new ColorMatrixColorFilter(new float[]{
+                -1.0f,     0,     0,    0, 255, // red
+                0, -1.0f,     0,    0, 255, // green
+                0,     0, -1.0f,    0, 255, // blue
+                0,     0,     0, 1.0f, 0    // alpha
+        });
+
+        PressedKeyView(Context c, String label) {
+            super(c);
+
+            final var dm = c.getResources().getDisplayMetrics();
+            final int keyViewSidePadding =
+                    (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, KEY_VIEW_SIDE_PADDING_DP, dm);
+            final int keyViewVerticalPadding =
+                    (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, KEY_VIEW_VERTICAL_PADDING_DP,
+                            dm);
+            final int keyViewMinWidth =
+                    (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, KEY_VIEW_MIN_WIDTH_DP, dm);
+            final int textSize =
+                    (int) TypedValue.applyDimension(COMPLEX_UNIT_SP, KEY_VIEW_TEXT_SIZE_SP, dm);
+
+            setText(label);
+            setGravity(Gravity.CENTER);
+            setMinimumWidth(keyViewMinWidth);
+            setTextSize(textSize);
+            setTypeface(Typeface.SANS_SERIF);
+            setBackgroundResource(R.drawable.focus_event_pressed_key_background);
+            setPaddingRelative(keyViewSidePadding, keyViewVerticalPadding, keyViewSidePadding,
+                    keyViewVerticalPadding);
+
+            setHighlighted(true);
+        }
+
+        void setHighlighted(boolean isHighlighted) {
+            if (isHighlighted) {
+                setTextColor(Color.BLACK);
+                getBackground().setColorFilter(sInvertColors);
+            } else {
+                setTextColor(Color.WHITE);
+                getBackground().clearColorFilter();
+            }
+            invalidate();
+        }
+    }
+
+    private static class PressedKeyContainer extends LinearLayout {
+
+        private final MarginLayoutParams mPressedKeyLayoutParams;
+
+        PressedKeyContainer(Context c) {
+            super(c);
+
+            final var dm = c.getResources().getDisplayMetrics();
+            final int keySeparationMargin =
+                    (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, KEY_SEPARATION_MARGIN_DP, dm);
+
+            final var transition = new LayoutTransition();
+            transition.disableTransitionType(LayoutTransition.APPEARING);
+            transition.disableTransitionType(LayoutTransition.DISAPPEARING);
+            transition.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
+            transition.setDuration(KEY_TRANSITION_DURATION_MILLIS);
+            setLayoutTransition(transition);
+
+            mPressedKeyLayoutParams = new MarginLayoutParams(WRAP_CONTENT, WRAP_CONTENT);
+            if (getOrientation() == VERTICAL) {
+                mPressedKeyLayoutParams.setMargins(0, keySeparationMargin, 0, 0);
+            } else {
+                mPressedKeyLayoutParams.setMargins(keySeparationMargin, 0, 0, 0);
+            }
+        }
+
+        public void handleKeyPressed(PressedKeyView pressedKeyView) {
+            addView(pressedKeyView, getChildCount(), mPressedKeyLayoutParams);
+            invalidate();
+        }
+
+        public void handleKeyRepeat(PressedKeyView repeatedKeyView) {
+            // Do nothing for now.
+        }
+
+        public void handleKeyRelease(PressedKeyView releasedKeyView) {
+            releasedKeyView.setHighlighted(false);
+            releasedKeyView.clearAnimation();
+            releasedKeyView.animate()
+                    .alpha(0)
+                    .setDuration(KEY_FADEOUT_DURATION_MILLIS)
+                    .setInterpolator(new AccelerateInterpolator())
+                    .withEndAction(this::cleanUpPressedKeyViews)
+                    .start();
+        }
+
+        private void cleanUpPressedKeyViews() {
+            int numChildrenToRemove = 0;
+            for (int i = 0; i < getChildCount(); i++) {
+                final View child = getChildAt(i);
+                if (child.getAlpha() != 0) {
+                    break;
+                }
+                child.setVisibility(View.GONE);
+                child.clearAnimation();
+                numChildrenToRemove++;
+            }
+            removeViews(0, numChildrenToRemove);
+            invalidate();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 5f45f91..662591e 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -18,6 +18,7 @@
 
 import static android.provider.DeviceConfig.NAMESPACE_INPUT_NATIVE_BOOT;
 import static android.view.KeyEvent.KEYCODE_UNKNOWN;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 
 import android.Manifest;
 import android.annotation.EnforcePermission;
@@ -32,6 +33,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.graphics.PixelFormat;
 import android.graphics.PointF;
 import android.hardware.SensorPrivacyManager;
 import android.hardware.SensorPrivacyManager.Sensors;
@@ -100,6 +102,7 @@
 import android.view.SurfaceControl;
 import android.view.VerifiedInputEvent;
 import android.view.ViewConfiguration;
+import android.view.WindowManager;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodSubtype;
 
@@ -386,6 +389,11 @@
     /** Whether to use the dev/input/event or uevent subsystem for the audio jack. */
     final boolean mUseDevInputEventForAudioJack;
 
+    private final Object mFocusEventDebugViewLock = new Object();
+    @GuardedBy("mFocusEventDebugViewLock")
+    @Nullable
+    private FocusEventDebugView mFocusEventDebugView;
+
     /** Point of injection for test dependencies. */
     @VisibleForTesting
     static class Injector {
@@ -427,7 +435,7 @@
         mContext = injector.getContext();
         mHandler = new InputManagerHandler(injector.getLooper());
         mNative = injector.getNativeService(this);
-        mSettingsObserver = new InputSettingsObserver(mContext, mHandler, mNative);
+        mSettingsObserver = new InputSettingsObserver(mContext, mHandler, this, mNative);
         mKeyboardLayoutManager = new KeyboardLayoutManager(mContext, mNative, mDataStore,
                 injector.getLooper());
         mBatteryController = new BatteryController(mContext, mNative, injector.getLooper());
@@ -2460,6 +2468,11 @@
     // Native callback.
     @SuppressWarnings("unused")
     private int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
+        synchronized (mFocusEventDebugViewLock) {
+            if (mFocusEventDebugView != null) {
+                mFocusEventDebugView.reportEvent(event);
+            }
+        }
         return mWindowManagerCallbacks.interceptKeyBeforeQueueing(event, policyFlags);
     }
 
@@ -3367,6 +3380,45 @@
         }
     }
 
+    void updateFocusEventDebugViewEnabled(boolean enabled) {
+        FocusEventDebugView view;
+        synchronized (mFocusEventDebugViewLock) {
+            if (enabled == (mFocusEventDebugView != null)) {
+                return;
+            }
+            if (enabled) {
+                mFocusEventDebugView = new FocusEventDebugView(mContext);
+                view = mFocusEventDebugView;
+            } else {
+                view = mFocusEventDebugView;
+                mFocusEventDebugView = null;
+            }
+        }
+        Objects.requireNonNull(view);
+
+        // Interact with WM outside the lock, since the lock is part of the input hotpath.
+        final WindowManager wm =
+                Objects.requireNonNull(mContext.getSystemService(WindowManager.class));
+        if (!enabled) {
+            wm.removeView(view);
+            return;
+        }
+
+        // TODO: Support multi display
+        final WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
+        lp.type = WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
+        lp.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+        lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+        lp.setFitInsetsTypes(0);
+        lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+        lp.format = PixelFormat.TRANSLUCENT;
+        lp.setTitle("FocusEventDebugView - display " + mContext.getDisplayId());
+        lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
+        wm.addView(view, lp);
+    }
+
     interface KeyboardBacklightControllerInterface {
         default void incrementKeyboardBacklight(int deviceId) {}
         default void decrementKeyboardBacklight(int deviceId) {}
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index 153e9c1..651063e 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -43,13 +43,16 @@
 
     private final Context mContext;
     private final Handler mHandler;
+    private final InputManagerService mService;
     private final NativeInputManagerService mNative;
     private final Map<Uri, Consumer<String /* reason*/>> mObservers;
 
-    InputSettingsObserver(Context context, Handler handler, NativeInputManagerService nativeIms) {
+    InputSettingsObserver(Context context, Handler handler, InputManagerService service,
+            NativeInputManagerService nativeIms) {
         super(handler);
         mContext = context;
         mHandler = handler;
+        mService = service;
         mNative = nativeIms;
         mObservers = Map.ofEntries(
                 Map.entry(Settings.System.getUriFor(Settings.System.POINTER_SPEED),
@@ -72,7 +75,9 @@
                 Map.entry(
                         Settings.Global.getUriFor(
                                 Settings.Global.MAXIMUM_OBSCURING_OPACITY_FOR_TOUCH),
-                        (reason) -> updateMaximumObscuringOpacityForTouch()));
+                        (reason) -> updateMaximumObscuringOpacityForTouch()),
+                Map.entry(Settings.System.getUriFor(Settings.System.SHOW_KEY_PRESSES),
+                        (reason) -> updateShowKeyPresses()));
     }
 
     /**
@@ -145,6 +150,11 @@
         mNative.setShowTouches(getBoolean(Settings.System.SHOW_TOUCHES, false));
     }
 
+    private void updateShowKeyPresses() {
+        mService.updateFocusEventDebugViewEnabled(
+                getBoolean(Settings.System.SHOW_KEY_PRESSES, false));
+    }
+
     private void updateAccessibilityLargePointer() {
         final int accessibilityConfig = Settings.Secure.getIntForUser(
                 mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON,
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
index f073756..24dbce4 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -128,18 +128,14 @@
     public static synchronized RecoverableKeyStoreManager
             getInstance(Context context) {
         if (mInstance == null) {
-            RecoverableKeyStoreDb db;
+            RecoverableKeyStoreDb db = RecoverableKeyStoreDb.newInstance(context);
             RemoteLockscreenValidationSessionStorage lockscreenCheckSessions;
             if (FeatureFlagUtils.isEnabled(context,
                     FeatureFlagUtils.SETTINGS_ENABLE_LOCKSCREEN_TRANSFER_API)) {
-                // TODO(b/254335492): Remove flag check when feature is launched.
-                db = RecoverableKeyStoreDb.newInstance(context, 7);
                 lockscreenCheckSessions = new RemoteLockscreenValidationSessionStorage();
             } else {
-                db = RecoverableKeyStoreDb.newInstance(context);
                 lockscreenCheckSessions = null;
             }
-
             PlatformKeyManager platformKeyManager;
             ApplicationKeyStorage applicationKeyStorage;
             try {
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
index 4a17e9a..d881769 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
@@ -78,18 +78,6 @@
         return new RecoverableKeyStoreDb(helper);
     }
 
-    /**
-     * A new instance, storing the database in the user directory of {@code context}.
-     *
-     * @hide
-     */
-    public static RecoverableKeyStoreDb newInstance(Context context, int version) {
-        RecoverableKeyStoreDbHelper helper = new RecoverableKeyStoreDbHelper(context, version);
-        helper.setWriteAheadLoggingEnabled(true);
-        helper.setIdleConnectionTimeout(IDLE_TIMEOUT_SECONDS);
-        return new RecoverableKeyStoreDb(helper);
-    }
-
     private RecoverableKeyStoreDb(RecoverableKeyStoreDbHelper keyStoreDbHelper) {
         this.mKeyStoreDbHelper = keyStoreDbHelper;
         this.mTestOnlyInsecureCertificateHelper = new TestOnlyInsecureCertificateHelper();
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
index 0e5e55c..386655a 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
@@ -34,7 +34,6 @@
     private static final String TAG = "RecoverableKeyStoreDbHp";
 
     // v6 - added user id serial number.
-    static final int DATABASE_VERSION = 6;
     // v7 - added bad guess counter for remote LSKF check;
     static final int DATABASE_VERSION_7 = 7;
     private static final String DATABASE_NAME = "recoverablekeystore.db";
@@ -118,23 +117,14 @@
         super(context, DATABASE_NAME, null, getDbVersion(context));
     }
 
-    RecoverableKeyStoreDbHelper(Context context, int version) {
-        super(context, DATABASE_NAME, null, version);
-    }
-
     private static int getDbVersion(Context context) {
-        // TODO(b/254335492): Update to version 7 and clean up code.
-        return DATABASE_VERSION;
+        return DATABASE_VERSION_7;
     }
 
     @Override
     public void onCreate(SQLiteDatabase db) {
         db.execSQL(SQL_CREATE_KEYS_ENTRY);
-        if (db.getVersion() == 6) { // always false
-            db.execSQL(SQL_CREATE_USER_METADATA_ENTRY);
-        } else {
-            db.execSQL(SQL_CREATE_USER_METADATA_ENTRY_FOR_V7);
-        }
+        db.execSQL(SQL_CREATE_USER_METADATA_ENTRY_FOR_V7);
         db.execSQL(SQL_CREATE_RECOVERY_SERVICE_METADATA_ENTRY);
         db.execSQL(SQL_CREATE_ROOT_OF_TRUST_ENTRY);
     }
diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java
index ac8ff21..ba5907c 100644
--- a/services/core/java/com/android/server/pm/AppsFilterImpl.java
+++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java
@@ -256,6 +256,7 @@
         private final PackageManagerInternal mPmInternal;
         private volatile boolean mFeatureEnabled =
                 PackageManager.APP_ENUMERATION_ENABLED_BY_DEFAULT;
+        @GuardedBy("mDisabledPackages")
         private final ArraySet<String> mDisabledPackages = new ArraySet<>();
 
         @Nullable
@@ -272,7 +273,9 @@
             mInjector = null;
             mPmInternal = null;
             mFeatureEnabled = orig.mFeatureEnabled;
-            mDisabledPackages.addAll(orig.mDisabledPackages);
+            synchronized (orig.mDisabledPackages) {
+                mDisabledPackages.addAll(orig.mDisabledPackages);
+            }
             mLoggingEnabled = orig.mLoggingEnabled;
         }
 
@@ -319,7 +322,9 @@
                 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "packageIsEnabled");
             }
             try {
-                return !mDisabledPackages.contains(pkg.getPackageName());
+                synchronized (mDisabledPackages) {
+                    return !mDisabledPackages.contains(pkg.getPackageName());
+                }
             } finally {
                 if (DEBUG_TRACING) {
                     Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
@@ -376,10 +381,12 @@
             final boolean enabled = mInjector.getCompatibility().isChangeEnabledInternalNoLogging(
                     PackageManager.FILTER_APPLICATION_QUERY,
                     AndroidPackageUtils.generateAppInfoWithoutState(pkg));
-            if (enabled) {
-                mDisabledPackages.remove(pkg.getPackageName());
-            } else {
-                mDisabledPackages.add(pkg.getPackageName());
+            synchronized (mDisabledPackages) {
+                if (enabled) {
+                    mDisabledPackages.remove(pkg.getPackageName());
+                } else {
+                    mDisabledPackages.add(pkg.getPackageName());
+                }
             }
             if (mAppsFilter != null) {
                 mAppsFilter.onChanged();
@@ -393,7 +400,9 @@
                     || setting.getPkg().isDebuggable());
             enableLogging(setting.getAppId(), enableLogging);
             if (removed) {
-                mDisabledPackages.remove(setting.getPackageName());
+                synchronized (mDisabledPackages) {
+                    mDisabledPackages.remove(setting.getPackageName());
+                }
                 if (mAppsFilter != null) {
                     mAppsFilter.onChanged();
                 }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 1721f83..a365194 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -1316,6 +1316,11 @@
 
         final var snapshot = mPm.snapshotComputer();
         final int callingUid = Binder.getCallingUid();
+        final var callingPackageName = snapshot.getNameForUid(callingUid);
+        if (!TextUtils.equals(callingPackageName, installerPackageName)) {
+            throw new SecurityException("The installerPackageName set by the caller doesn't match "
+                    + "the caller's own package name.");
+        }
         if (!PackageManagerServiceUtils.isSystemOrRootOrShell(callingUid)) {
             for (var packageName : packageNames) {
                 var ps = snapshot.getPackageStateInternal(packageName);
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index 3492b26..f41d964 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -23,7 +23,6 @@
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.MODE_ERRORED;
 import static android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_DEFAULT;
-import static android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_DENIED;
 import static android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED;
 import static android.content.pm.PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION;
@@ -3657,25 +3656,6 @@
         for (String permission : pkg.getRequestedPermissions()) {
             Integer permissionState = permissionStates.get(permission);
 
-            if (Objects.equals(permission, Manifest.permission.USE_FULL_SCREEN_INTENT)
-                    && permissionState == null) {
-                final PackageStateInternal ps;
-                final long token = Binder.clearCallingIdentity();
-                try {
-                    ps = mPackageManagerInt.getPackageStateInternal(pkg.getPackageName());
-                } finally {
-                    Binder.restoreCallingIdentity(token);
-                }
-                final String[] useFullScreenIntentPackageNames =
-                        mContext.getResources().getStringArray(
-                                com.android.internal.R.array.config_useFullScreenIntentPackages);
-                final boolean canUseFullScreenIntent = (ps != null && ps.isSystem())
-                        || ArrayUtils.contains(useFullScreenIntentPackageNames,
-                                pkg.getPackageName());
-                permissionState = canUseFullScreenIntent ? PERMISSION_STATE_GRANTED
-                        : PERMISSION_STATE_DENIED;
-            }
-
             if (permissionState == null || permissionState == PERMISSION_STATE_DEFAULT) {
                 continue;
             }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index dc56def..d7eff52 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3096,18 +3096,21 @@
                     }
                     int screenDisplayId = displayId < 0 ? DEFAULT_DISPLAY : displayId;
 
+                    float minLinearBrightness = mPowerManager.getBrightnessConstraint(
+                            PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
+                    float maxLinearBrightness = mPowerManager.getBrightnessConstraint(
+                            PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
                     float linearBrightness = mDisplayManager.getBrightness(screenDisplayId);
 
                     float gammaBrightness = BrightnessUtils.convertLinearToGamma(linearBrightness);
                     float adjustedGammaBrightness =
                             gammaBrightness + 1f / BRIGHTNESS_STEPS * direction;
-
+                    adjustedGammaBrightness = MathUtils.constrain(adjustedGammaBrightness, 0f,
+                            1f);
                     float adjustedLinearBrightness = BrightnessUtils.convertGammaToLinear(
                             adjustedGammaBrightness);
-
-                    adjustedLinearBrightness = MathUtils.constrain(adjustedLinearBrightness, 0f,
-                            1f);
-
+                    adjustedLinearBrightness = MathUtils.constrain(adjustedLinearBrightness,
+                            minLinearBrightness, maxLinearBrightness);
                     mDisplayManager.setBrightness(screenDisplayId, adjustedLinearBrightness);
 
                     startActivityAsUser(new Intent(Intent.ACTION_SHOW_BRIGHTNESS_DIALOG),
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 6f64070..46aa387 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -514,6 +514,7 @@
             } catch (RemoteException e) {
                 // if this fails we don't really care; the setting app may just
                 // have crashed and that sort of thing is a fact of life.
+                Slog.w(TAG, "onWallpaperChanged threw an exception", e);
             }
         }
     }
@@ -524,7 +525,7 @@
             try {
                 cb.onWallpaperChanged();
             } catch (RemoteException e) {
-                // Oh well it went away; no big deal
+                Slog.w(TAG, "Failed to notify keyguard callback about wallpaper changes", e);
             }
         }
     }
@@ -620,6 +621,7 @@
             } catch (RemoteException e) {
                 // Callback is gone, it's not necessary to unregister it since
                 // RemoteCallbackList#getBroadcastItem will take care of it.
+                Slog.w(TAG, "onWallpaperColorsChanged() threw an exception", e);
             }
         }
 
@@ -628,7 +630,7 @@
             try {
                 keyguardListener.onWallpaperColorsChanged(wallpaperColors, which, userId);
             } catch (RemoteException e) {
-                // Oh well it went away; no big deal
+                Slog.w(TAG, "keyguardListener.onWallpaperColorsChanged threw an exception", e);
             }
         }
     }
@@ -965,7 +967,7 @@
                     connection.mService.detach(mToken);
                 }
             } catch (RemoteException e) {
-                Slog.w(TAG, "connection.mService.destroy() threw a RemoteException");
+                Slog.w(TAG, "connection.mService.destroy() threw a RemoteException", e);
             }
             mEngine = null;
         }
@@ -1117,7 +1119,7 @@
                     try {
                         cb.onColorsChanged(area, colors);
                     } catch (RemoteException e) {
-                        e.printStackTrace();
+                        Slog.w(TAG, "Failed to notify local color callbacks", e);
                     }
                 };
                 synchronized (mLock) {
@@ -1316,7 +1318,7 @@
                     try {
                         mReply.sendResult(null);
                     } catch (RemoteException e) {
-                        Slog.d(TAG, "failed to send callback!", e);
+                        Slog.d(TAG, "Failed to send callback!", e);
                     } finally {
                         Binder.restoreCallingIdentity(ident);
                     }
@@ -1583,7 +1585,7 @@
         mShuttingDown = false;
         mImageWallpaper = ComponentName.unflattenFromString(
                 context.getResources().getString(R.string.image_wallpaper_component));
-        mDefaultWallpaperComponent = WallpaperManager.getDefaultWallpaperComponent(context);
+        mDefaultWallpaperComponent = WallpaperManager.getCmfDefaultWallpaperComponent(context);
         mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
         mIPackageManager = AppGlobals.getPackageManager();
@@ -1909,7 +1911,8 @@
                 try {
                     si = mIPackageManager.getServiceInfo(cname,
                             PackageManager.MATCH_DIRECT_BOOT_UNAWARE, wallpaper.userId);
-                } catch (RemoteException ignored) {
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Failure starting previous wallpaper; clearing", e);
                 }
 
                 if (mIsLockscreenLiveWallpaperEnabled) {
@@ -1918,7 +1921,6 @@
                 }
 
                 if (si == null) {
-                    Slog.w(TAG, "Failure starting previous wallpaper; clearing");
                     clearWallpaperLocked(false, FLAG_SYSTEM, wallpaper.userId, reply);
                 } else {
                     Slog.w(TAG, "Wallpaper isn't direct boot aware; using fallback until unlocked");
@@ -1942,8 +1944,6 @@
             WallpaperData wallpaper, IRemoteCallback reply, ServiceInfo serviceInfo) {
 
         if (serviceInfo == null) {
-            Slog.w(TAG, "Failure starting previous wallpaper; clearing");
-
             if (wallpaper.mWhich == (FLAG_LOCK | FLAG_SYSTEM)) {
                 clearWallpaperLocked(false, FLAG_SYSTEM, wallpaper.userId, null);
                 clearWallpaperLocked(false, FLAG_LOCK, wallpaper.userId, reply);
@@ -2042,7 +2042,7 @@
                         try {
                             cb.onWallpaperChanged();
                         } catch (RemoteException e) {
-                            // Oh well it went away; no big deal
+                            Slog.w(TAG, "Failed to notify keyguard after wallpaper clear", e);
                         }
                     }
                     saveSettingsLocked(userId);
@@ -2074,6 +2074,7 @@
                 try {
                     reply.sendResult(null);
                 } catch (RemoteException e1) {
+                    Slog.w(TAG, "Failed to notify callback after wallpaper clear", e1);
                 }
             }
         } finally {
@@ -2168,6 +2169,7 @@
                         try {
                             engine.setDesiredSize(width, height);
                         } catch (RemoteException e) {
+                            Slog.w(TAG, "Failed to set desired size", e);
                         }
                         notifyCallbacksLocked(wallpaper);
                     } else if (wallpaper.connection.mService != null && connector != null) {
@@ -2263,6 +2265,7 @@
                         try {
                             engine.setDisplayPadding(padding);
                         } catch (RemoteException e) {
+                            Slog.w(TAG, "Failed to set display padding", e);
                         }
                         notifyCallbacksLocked(wallpaper);
                     } else if (wallpaper.connection.mService != null && connector != null) {
@@ -2498,7 +2501,7 @@
             try {
                 engine.setInAmbientMode(inAmbientMode, animationDuration);
             } catch (RemoteException e) {
-                // Cannot talk to wallpaper engine.
+                Slog.w(TAG, "Failed to set ambient mode", e);
             }
         }
     }
@@ -2532,7 +2535,7 @@
                                     displayConnector.mEngine.dispatchWallpaperCommand(
                                             WallpaperManager.COMMAND_WAKING_UP, x, y, -1, extras);
                                 } catch (RemoteException e) {
-                                    e.printStackTrace();
+                                    Slog.w(TAG, "Failed to dispatch COMMAND_WAKING_UP", e);
                                 }
                             }
                         });
@@ -2571,7 +2574,7 @@
                                             WallpaperManager.COMMAND_GOING_TO_SLEEP, x, y, -1,
                                             extras);
                                 } catch (RemoteException e) {
-                                    e.printStackTrace();
+                                    Slog.w(TAG, "Failed to dispatch COMMAND_GOING_TO_SLEEP", e);
                                 }
                             }
                         });
@@ -2611,7 +2614,7 @@
                     try {
                         engine.onScreenTurnedOn();
                     } catch (RemoteException e) {
-                        e.printStackTrace();
+                        Slog.w(TAG, "Failed to notify that the screen turned on", e);
                     }
                 }
             }
@@ -2652,7 +2655,7 @@
                     try {
                         engine.onScreenTurningOn();
                     } catch (RemoteException e) {
-                        e.printStackTrace();
+                        Slog.w(TAG, "Failed to notify that the screen is turning on", e);
                     }
                 }
             }
@@ -2690,7 +2693,7 @@
                                     WallpaperManager.COMMAND_KEYGUARD_GOING_AWAY,
                                     -1, -1, -1, new Bundle());
                         } catch (RemoteException e) {
-                            e.printStackTrace();
+                            Slog.w(TAG, "Failed to notify that the keyguard is going away", e);
                         }
                     }
                 });
@@ -2789,6 +2792,20 @@
     }
 
     /**
+     * Returns true if there is a static wallpaper on the specified screen. With which=FLAG_LOCK,
+     * always return false if the lockscreen doesn't run its own wallpaper engine.
+     */
+    @Override
+    public boolean isStaticWallpaper(int which) {
+        synchronized (mLock) {
+            WallpaperData wallpaperData = (which == FLAG_LOCK ? mLockWallpaperMap : mWallpaperMap)
+                    .get(mCurrentUserId);
+            if (wallpaperData == null) return false;
+            return mImageWallpaper.equals(wallpaperData.wallpaperComponent);
+        }
+    }
+
+    /**
      * Sets wallpaper dim amount for the calling UID. This applies to all destinations (home, lock)
      * with an active wallpaper engine.
      *
@@ -2839,10 +2856,8 @@
                                     try {
                                         connector.mEngine.applyDimming(maxDimAmount);
                                     } catch (RemoteException e) {
-                                        Slog.w(TAG,
-                                                "Can't apply dimming on wallpaper display "
-                                                        + "connector",
-                                                e);
+                                        Slog.w(TAG, "Can't apply dimming on wallpaper display "
+                                                        + "connector", e);
                                     }
                                 }
                             });
@@ -3559,6 +3574,7 @@
                 try {
                     wallpaper.connection.mReply.sendResult(null);
                 } catch (RemoteException e) {
+                    Slog.w(TAG, "Error sending reply to wallpaper before disconnect", e);
                 }
                 wallpaper.connection.mReply = null;
             }
@@ -3626,6 +3642,7 @@
 
                 // The RemoteCallbackList will take care of removing
                 // the dead object for us.
+                Slog.w(TAG, "Failed to notify callbacks about wallpaper changes", e);
             }
         }
         wallpaper.callbacks.finishBroadcast();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 3f4a775..7926216 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -178,6 +178,9 @@
     /**
      * Returns the top activity from each of the currently visible root tasks, and the related task
      * id. The first entry will be the focused activity.
+     *
+     * <p>NOTE: If the top activity is in the split screen, the other activities in the same split
+     * screen will also be returned.
      */
     public abstract List<ActivityAssistInfo> getTopVisibleActivities();
 
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 57812c1..fa5da30 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1884,7 +1884,7 @@
 
     /** Returns {@code true} if the IME is possible to show on the launching activity. */
     boolean mayImeShowOnLaunchingActivity(@NonNull ActivityRecord r) {
-        final WindowState win = r.findMainWindow();
+        final WindowState win = r.findMainWindow(false /* exclude starting window */);
         if (win == null) {
             return false;
         }
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 76fd693..911591c 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -22,6 +22,7 @@
 import static android.view.InsetsFrameProvider.SOURCE_CONTAINER_BOUNDS;
 import static android.view.InsetsFrameProvider.SOURCE_DISPLAY;
 import static android.view.InsetsFrameProvider.SOURCE_FRAME;
+import static android.view.ViewRootImpl.CLIENT_TRANSIENT;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
@@ -213,7 +214,8 @@
         }
     }
 
-    private final SystemGesturesPointerEventListener mSystemGestures;
+    // Will be null in client transient mode.
+    private SystemGesturesPointerEventListener mSystemGestures;
 
     final DecorInsets mDecorInsets;
 
@@ -408,158 +410,162 @@
         final Looper looper = UiThread.getHandler().getLooper();
         mHandler = new PolicyHandler(looper);
         // TODO(b/181821798) Migrate SystemGesturesPointerEventListener to use window context.
-        mSystemGestures = new SystemGesturesPointerEventListener(mUiContext, mHandler,
-                new SystemGesturesPointerEventListener.Callbacks() {
+        if (!CLIENT_TRANSIENT) {
+            SystemGesturesPointerEventListener.Callbacks gesturesPointerEventCallbacks =
+                    new SystemGesturesPointerEventListener.Callbacks() {
 
-                    private static final long MOUSE_GESTURE_DELAY_MS = 500;
+                private static final long MOUSE_GESTURE_DELAY_MS = 500;
 
-                    private Runnable mOnSwipeFromLeft = this::onSwipeFromLeft;
-                    private Runnable mOnSwipeFromTop = this::onSwipeFromTop;
-                    private Runnable mOnSwipeFromRight = this::onSwipeFromRight;
-                    private Runnable mOnSwipeFromBottom = this::onSwipeFromBottom;
+                private Runnable mOnSwipeFromLeft = this::onSwipeFromLeft;
+                private Runnable mOnSwipeFromTop = this::onSwipeFromTop;
+                private Runnable mOnSwipeFromRight = this::onSwipeFromRight;
+                private Runnable mOnSwipeFromBottom = this::onSwipeFromBottom;
 
-                    private Insets getControllableInsets(WindowState win) {
-                        if (win == null) {
-                            return Insets.NONE;
-                        }
-                        final InsetsSourceProvider provider = win.getControllableInsetProvider();
-                        if (provider == null) {
-                            return  Insets.NONE;
-                        }
-                        return provider.getSource().calculateInsets(win.getBounds(),
-                                true /* ignoreVisibility */);
+                private Insets getControllableInsets(WindowState win) {
+                    if (win == null) {
+                        return Insets.NONE;
                     }
+                    final InsetsSourceProvider provider = win.getControllableInsetProvider();
+                    if (provider == null) {
+                        return Insets.NONE;
+                    }
+                    return provider.getSource().calculateInsets(win.getBounds(),
+                            true /* ignoreVisibility */);
+                }
 
-                    @Override
-                    public void onSwipeFromTop() {
-                        synchronized (mLock) {
-                            requestTransientBars(mTopGestureHost,
-                                    getControllableInsets(mTopGestureHost).top > 0);
+                @Override
+                public void onSwipeFromTop() {
+                    synchronized (mLock) {
+                        requestTransientBars(mTopGestureHost,
+                                getControllableInsets(mTopGestureHost).top > 0);
+                    }
+                }
+
+                @Override
+                public void onSwipeFromBottom() {
+                    synchronized (mLock) {
+                        requestTransientBars(mBottomGestureHost,
+                                getControllableInsets(mBottomGestureHost).bottom > 0);
+                    }
+                }
+
+                private boolean allowsSideSwipe(Region excludedRegion) {
+                    return mNavigationBarAlwaysShowOnSideGesture
+                            && !mSystemGestures.currentGestureStartedInRegion(excludedRegion);
+                }
+
+                @Override
+                public void onSwipeFromRight() {
+                    final Region excludedRegion = Region.obtain();
+                    synchronized (mLock) {
+                        mDisplayContent.calculateSystemGestureExclusion(
+                                excludedRegion, null /* outUnrestricted */);
+                        final boolean hasWindow =
+                                getControllableInsets(mRightGestureHost).right > 0;
+                        if (hasWindow || allowsSideSwipe(excludedRegion)) {
+                            requestTransientBars(mRightGestureHost, hasWindow);
                         }
                     }
+                    excludedRegion.recycle();
+                }
 
-                    @Override
-                    public void onSwipeFromBottom() {
-                        synchronized (mLock) {
-                            requestTransientBars(mBottomGestureHost,
-                                    getControllableInsets(mBottomGestureHost).bottom > 0);
+                @Override
+                public void onSwipeFromLeft() {
+                    final Region excludedRegion = Region.obtain();
+                    synchronized (mLock) {
+                        mDisplayContent.calculateSystemGestureExclusion(
+                                excludedRegion, null /* outUnrestricted */);
+                        final boolean hasWindow =
+                                getControllableInsets(mLeftGestureHost).left > 0;
+                        if (hasWindow || allowsSideSwipe(excludedRegion)) {
+                            requestTransientBars(mLeftGestureHost, hasWindow);
                         }
                     }
+                    excludedRegion.recycle();
+                }
 
-                    private boolean allowsSideSwipe(Region excludedRegion) {
-                        return mNavigationBarAlwaysShowOnSideGesture
-                                && !mSystemGestures.currentGestureStartedInRegion(excludedRegion);
+                @Override
+                public void onFling(int duration) {
+                    if (mService.mPowerManagerInternal != null) {
+                        mService.mPowerManagerInternal.setPowerBoost(
+                                Boost.INTERACTION, duration);
                     }
+                }
 
-                    @Override
-                    public void onSwipeFromRight() {
-                        final Region excludedRegion = Region.obtain();
-                        synchronized (mLock) {
-                            mDisplayContent.calculateSystemGestureExclusion(
-                                    excludedRegion, null /* outUnrestricted */);
-                            final boolean hasWindow =
-                                    getControllableInsets(mRightGestureHost).right > 0;
-                            if (hasWindow || allowsSideSwipe(excludedRegion)) {
-                                requestTransientBars(mRightGestureHost, hasWindow);
-                            }
-                        }
-                        excludedRegion.recycle();
-                    }
+                @Override
+                public void onDebug() {
+                    // no-op
+                }
 
-                    @Override
-                    public void onSwipeFromLeft() {
-                        final Region excludedRegion = Region.obtain();
-                        synchronized (mLock) {
-                            mDisplayContent.calculateSystemGestureExclusion(
-                                    excludedRegion, null /* outUnrestricted */);
-                            final boolean hasWindow =
-                                    getControllableInsets(mLeftGestureHost).left > 0;
-                            if (hasWindow || allowsSideSwipe(excludedRegion)) {
-                                requestTransientBars(mLeftGestureHost, hasWindow);
-                            }
-                        }
-                        excludedRegion.recycle();
-                    }
+                private WindowOrientationListener getOrientationListener() {
+                    final DisplayRotation rotation = mDisplayContent.getDisplayRotation();
+                    return rotation != null ? rotation.getOrientationListener() : null;
+                }
 
-                    @Override
-                    public void onFling(int duration) {
-                        if (mService.mPowerManagerInternal != null) {
-                            mService.mPowerManagerInternal.setPowerBoost(
-                                    Boost.INTERACTION, duration);
-                        }
+                @Override
+                public void onDown() {
+                    final WindowOrientationListener listener = getOrientationListener();
+                    if (listener != null) {
+                        listener.onTouchStart();
                     }
+                }
 
-                    @Override
-                    public void onDebug() {
-                        // no-op
+                @Override
+                public void onUpOrCancel() {
+                    final WindowOrientationListener listener = getOrientationListener();
+                    if (listener != null) {
+                        listener.onTouchEnd();
                     }
+                }
 
-                    private WindowOrientationListener getOrientationListener() {
-                        final DisplayRotation rotation = mDisplayContent.getDisplayRotation();
-                        return rotation != null ? rotation.getOrientationListener() : null;
-                    }
+                @Override
+                public void onMouseHoverAtLeft() {
+                    mHandler.removeCallbacks(mOnSwipeFromLeft);
+                    mHandler.postDelayed(mOnSwipeFromLeft, MOUSE_GESTURE_DELAY_MS);
+                }
 
-                    @Override
-                    public void onDown() {
-                        final WindowOrientationListener listener = getOrientationListener();
-                        if (listener != null) {
-                            listener.onTouchStart();
-                        }
-                    }
+                @Override
+                public void onMouseHoverAtTop() {
+                    mHandler.removeCallbacks(mOnSwipeFromTop);
+                    mHandler.postDelayed(mOnSwipeFromTop, MOUSE_GESTURE_DELAY_MS);
+                }
 
-                    @Override
-                    public void onUpOrCancel() {
-                        final WindowOrientationListener listener = getOrientationListener();
-                        if (listener != null) {
-                            listener.onTouchEnd();
-                        }
-                    }
+                @Override
+                public void onMouseHoverAtRight() {
+                    mHandler.removeCallbacks(mOnSwipeFromRight);
+                    mHandler.postDelayed(mOnSwipeFromRight, MOUSE_GESTURE_DELAY_MS);
+                }
 
-                    @Override
-                    public void onMouseHoverAtLeft() {
-                        mHandler.removeCallbacks(mOnSwipeFromLeft);
-                        mHandler.postDelayed(mOnSwipeFromLeft, MOUSE_GESTURE_DELAY_MS);
-                    }
+                @Override
+                public void onMouseHoverAtBottom() {
+                    mHandler.removeCallbacks(mOnSwipeFromBottom);
+                    mHandler.postDelayed(mOnSwipeFromBottom, MOUSE_GESTURE_DELAY_MS);
+                }
 
-                    @Override
-                    public void onMouseHoverAtTop() {
-                        mHandler.removeCallbacks(mOnSwipeFromTop);
-                        mHandler.postDelayed(mOnSwipeFromTop, MOUSE_GESTURE_DELAY_MS);
-                    }
+                @Override
+                public void onMouseLeaveFromLeft() {
+                    mHandler.removeCallbacks(mOnSwipeFromLeft);
+                }
 
-                    @Override
-                    public void onMouseHoverAtRight() {
-                        mHandler.removeCallbacks(mOnSwipeFromRight);
-                        mHandler.postDelayed(mOnSwipeFromRight, MOUSE_GESTURE_DELAY_MS);
-                    }
+                @Override
+                public void onMouseLeaveFromTop() {
+                    mHandler.removeCallbacks(mOnSwipeFromTop);
+                }
 
-                    @Override
-                    public void onMouseHoverAtBottom() {
-                        mHandler.removeCallbacks(mOnSwipeFromBottom);
-                        mHandler.postDelayed(mOnSwipeFromBottom, MOUSE_GESTURE_DELAY_MS);
-                    }
+                @Override
+                public void onMouseLeaveFromRight() {
+                    mHandler.removeCallbacks(mOnSwipeFromRight);
+                }
 
-                    @Override
-                    public void onMouseLeaveFromLeft() {
-                        mHandler.removeCallbacks(mOnSwipeFromLeft);
-                    }
-
-                    @Override
-                    public void onMouseLeaveFromTop() {
-                        mHandler.removeCallbacks(mOnSwipeFromTop);
-                    }
-
-                    @Override
-                    public void onMouseLeaveFromRight() {
-                        mHandler.removeCallbacks(mOnSwipeFromRight);
-                    }
-
-                    @Override
-                    public void onMouseLeaveFromBottom() {
-                        mHandler.removeCallbacks(mOnSwipeFromBottom);
-                    }
-                });
-        displayContent.registerPointerEventListener(mSystemGestures);
+                @Override
+                public void onMouseLeaveFromBottom() {
+                    mHandler.removeCallbacks(mOnSwipeFromBottom);
+                }
+            };
+            mSystemGestures = new SystemGesturesPointerEventListener(mUiContext, mHandler,
+                    gesturesPointerEventCallbacks);
+            displayContent.registerPointerEventListener(mSystemGestures);
+        }
         mAppTransitionListener = new WindowManagerInternal.AppTransitionListener() {
 
             private Runnable mAppTransitionPending = () -> {
@@ -645,7 +651,9 @@
                 mContext, () -> {
             synchronized (mLock) {
                 onConfigurationChanged();
-                mSystemGestures.onConfigurationChanged();
+                if (!CLIENT_TRANSIENT) {
+                    mSystemGestures.onConfigurationChanged();
+                }
                 mDisplayContent.updateSystemGestureExclusion();
             }
         });
@@ -667,7 +675,9 @@
     }
 
     void systemReady() {
-        mSystemGestures.systemReady();
+        if (!CLIENT_TRANSIENT) {
+            mSystemGestures.systemReady();
+        }
         if (mService.mPointerLocationEnabled) {
             setPointerLocationEnabled(true);
         }
@@ -1308,7 +1318,9 @@
     }
 
     void onDisplayInfoChanged(DisplayInfo info) {
-        mSystemGestures.onDisplayInfoChanged(info);
+        if (!CLIENT_TRANSIENT) {
+            mSystemGestures.onDisplayInfoChanged(info);
+        }
     }
 
     /**
@@ -1681,7 +1693,9 @@
         // Update the latest display size, cutout.
         mDisplayContent.updateDisplayInfo();
         onConfigurationChanged();
-        mSystemGestures.onConfigurationChanged();
+        if (!CLIENT_TRANSIENT) {
+            mSystemGestures.onConfigurationChanged();
+        }
     }
 
     /**
@@ -1960,6 +1974,9 @@
 
     @VisibleForTesting
     void requestTransientBars(WindowState swipeTarget, boolean isGestureOnSystemBar) {
+        if (CLIENT_TRANSIENT) {
+            return;
+        }
         if (swipeTarget == null || !mService.mPolicy.isUserSetupComplete()) {
             // Swipe-up for navigation bar is disabled during setup
             return;
@@ -2608,7 +2625,9 @@
             final DecorInsets.Info info = mDecorInsets.mInfoForRotation[rotation];
             pw.println(prefixInner + Surface.rotationToString(rotation) + "=" + info);
         }
-        mSystemGestures.dump(pw, prefix);
+        if (!CLIENT_TRANSIENT) {
+            mSystemGestures.dump(pw, prefix);
+        }
 
         pw.print(prefix); pw.println("Looper state:");
         mHandler.getLooper().dump(new PrintWriterPrinter(pw), prefix + "  ");
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index fc99f4c..0288e4b 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -832,13 +832,14 @@
 
             // Get the bounds of the "space-to-fill". The transformed bounds have the highest
             // priority because the activity is launched in a rotated environment. In multi-window
-            // mode, the task-level represents this. In fullscreen-mode, the task container does
+            // mode, the taskFragment-level represents this for both split-screen
+            // and activity-embedding. In fullscreen-mode, the task container does
             // (since the orientation letterbox is also applied to the task).
             final Rect transformedBounds = mActivityRecord.getFixedRotationTransformDisplayBounds();
             final Rect spaceToFill = transformedBounds != null
                     ? transformedBounds
                     : mActivityRecord.inMultiWindowMode()
-                            ? mActivityRecord.getTask().getBounds()
+                            ? mActivityRecord.getTaskFragment().getBounds()
                             : mActivityRecord.getRootTask().getParent().getBounds();
             // In case of translucent activities an option is to use the WindowState#getFrame() of
             // the first opaque activity beneath. In some cases (e.g. an opaque activity is using
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 5149985..0074ebd 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1746,9 +1746,13 @@
     /**
      * @return a list of pairs, containing activities and their task id which are the top ones in
      * each visible root task. The first entry will be the focused activity.
+     *
+     * <p>NOTE: If the top activity is in the split screen, the other activities in the same split
+     * screen will also be returned.
      */
     List<ActivityAssistInfo> getTopVisibleActivities() {
         final ArrayList<ActivityAssistInfo> topVisibleActivities = new ArrayList<>();
+        final ArrayList<ActivityAssistInfo> activityAssistInfos = new ArrayList<>();
         final Task topFocusedRootTask = getTopDisplayFocusedRootTask();
         // Traverse all displays.
         forAllRootTasks(rootTask -> {
@@ -1756,11 +1760,21 @@
             if (rootTask.shouldBeVisible(null /* starting */)) {
                 final ActivityRecord top = rootTask.getTopNonFinishingActivity();
                 if (top != null) {
-                    ActivityAssistInfo visibleActivity = new ActivityAssistInfo(top);
+                    activityAssistInfos.clear();
+                    activityAssistInfos.add(new ActivityAssistInfo(top));
+                    // Check if the activity on the split screen.
+                    final Task adjacentTask = top.getTask().getAdjacentTask();
+                    if (adjacentTask != null) {
+                        final ActivityRecord adjacentActivityRecord =
+                                adjacentTask.getTopNonFinishingActivity();
+                        if (adjacentActivityRecord != null) {
+                            activityAssistInfos.add(new ActivityAssistInfo(adjacentActivityRecord));
+                        }
+                    }
                     if (rootTask == topFocusedRootTask) {
-                        topVisibleActivities.add(0, visibleActivity);
+                        topVisibleActivities.addAll(0, activityAssistInfos);
                     } else {
-                        topVisibleActivities.add(visibleActivity);
+                        topVisibleActivities.addAll(activityAssistInfos);
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 931c258..b7c29bf 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5170,6 +5170,12 @@
             // task.
             r.setVisibility(true);
             ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
+            // If launching behind, the app will start regardless of what's above it, so mark it
+            // as unknown even before prior `pause`. This also prevents a race between set-ready
+            // and activityPause. Launch-behind is basically only used for dream now.
+            if (!r.isVisibleRequested()) {
+                r.notifyUnknownVisibilityLaunchedForKeyguardTransition();
+            }
             // Go ahead to execute app transition for this activity since the app transition
             // will not be triggered through the resume channel.
             mDisplayContent.executeAppTransition();
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 829a33d..be5f141 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -1416,6 +1416,9 @@
     }
 
     void onAppTransitionDone() {
+        if (mSurfaceFreezer.hasLeash()) {
+            mSurfaceFreezer.unfreeze(getSyncTransaction());
+        }
         for (int i = mChildren.size() - 1; i >= 0; --i) {
             final WindowContainer wc = mChildren.get(i);
             wc.onAppTransitionDone();
@@ -3990,6 +3993,9 @@
             }
             // Otherwise this is the "root" of a synced subtree, so continue on to preparation.
         }
+        if (oldParent != null && newParent != null && !shouldUpdateSyncOnReparent()) {
+            return;
+        }
 
         // This container's situation has changed so we need to restart its sync.
         // We cannot reset the sync without a chance of a deadlock since it will request a new
@@ -4004,6 +4010,11 @@
         prepareSync();
     }
 
+    /** Returns {@code true} if {@link #mSyncState} needs to be updated when reparenting. */
+    protected boolean shouldUpdateSyncOnReparent() {
+        return true;
+    }
+
     void registerWindowContainerListener(WindowContainerListener listener) {
         registerWindowContainerListener(listener, true /* shouldPropConfig */);
     }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 8baf048..1a2b57c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2675,7 +2675,7 @@
     void finishDrawingWindow(Session session, IWindow client,
             @Nullable SurfaceControl.Transaction postDrawTransaction, int seqId) {
         if (postDrawTransaction != null) {
-            postDrawTransaction.sanitize();
+            postDrawTransaction.sanitize(Binder.getCallingPid(), Binder.getCallingUid());
         }
 
         final long origId = Binder.clearCallingIdentity();
@@ -3922,15 +3922,17 @@
 
     /**
      * Returns the touch mode state for the display id passed as argument.
+     *
+     * This method will return the default touch mode state (represented by
+     * {@code com.android.internal.R.bool.config_defaultInTouchMode}) if the display passed as
+     * argument is no longer registered in {@RootWindowContainer}).
      */
     @Override  // Binder call
     public boolean isInTouchMode(int displayId) {
         synchronized (mGlobalLock) {
             final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
             if (displayContent == null) {
-                throw new IllegalStateException("Failed to retrieve the touch mode state for"
-                        + "display {" + displayId + "}: display is not registered in "
-                        + "WindowRootContainer");
+                return mContext.getResources().getBoolean(R.bool.config_defaultInTouchMode);
             }
             return displayContent.isInTouchMode();
         }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 032f08a..bab7a78 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5635,6 +5635,13 @@
     }
 
     @Override
+    protected boolean shouldUpdateSyncOnReparent() {
+        // Keep the sync state in case the client is drawing for the latest conifguration or the
+        // configuration is not changed after reparenting. This avoids a redundant redraw request.
+        return mSyncState != SYNC_STATE_NONE && !mLastConfigReportedToClient;
+    }
+
+    @Override
     boolean prepareSync() {
         if (!mDrawHandlers.isEmpty()) {
             Slog.w(TAG, "prepareSync with mDrawHandlers, " + this + ", " + Debug.getCallers(8));
diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
index 04ecd6e..e3d4c22 100644
--- a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
@@ -19,6 +19,7 @@
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
+import android.credentials.ClearCredentialStateException;
 import android.credentials.ClearCredentialStateRequest;
 import android.credentials.CredentialProviderInfo;
 import android.credentials.IClearCredentialStateCallback;
@@ -29,6 +30,8 @@
 import android.service.credentials.CallingAppInfo;
 import android.util.Slog;
 
+import com.android.server.credentials.metrics.ProviderSessionMetric;
+
 import java.util.ArrayList;
 import java.util.Set;
 
@@ -92,9 +95,12 @@
     public void onFinalResponseReceived(
             ComponentName componentName,
             Void response) {
-        mRequestSessionMetric.collectChosenMetricViaCandidateTransfer(
-                mProviders.get(componentName.flattenToString()).mProviderSessionMetric
-                        .getCandidatePhasePerProviderMetric());
+        if (mProviders.get(componentName.flattenToString()) != null) {
+            ProviderSessionMetric providerSessionMetric =
+                    mProviders.get(componentName.flattenToString()).mProviderSessionMetric;
+            mRequestSessionMetric.collectChosenMetricViaCandidateTransfer(providerSessionMetric
+                    .getCandidatePhasePerProviderMetric());
+        }
         respondToClientWithResponseAndFinish(null);
     }
 
@@ -141,8 +147,9 @@
                 return;
             }
         }
-        // TODO: Replace with properly defined error type
-        respondToClientWithErrorAndFinish("UNKNOWN", "All providers failed");
+        String exception = ClearCredentialStateException.TYPE_UNKNOWN;
+        mRequestSessionMetric.collectFrameworkException(exception);
+        respondToClientWithErrorAndFinish(exception, "All providers failed");
     }
 
     @Override
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index 0af5647..2d6e74f 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -35,6 +35,7 @@
 import android.service.credentials.PermissionUtils;
 import android.util.Slog;
 
+import com.android.server.credentials.metrics.ProviderSessionMetric;
 import com.android.server.credentials.metrics.ProviderStatusForMetrics;
 
 import java.util.ArrayList;
@@ -131,9 +132,12 @@
             @Nullable CreateCredentialResponse response) {
         Slog.i(TAG, "Final credential received from: " + componentName.flattenToString());
         mRequestSessionMetric.collectUiResponseData(/*uiReturned=*/ true, System.nanoTime());
-        mRequestSessionMetric.collectChosenMetricViaCandidateTransfer(mProviders.get(
-                componentName.flattenToString()).mProviderSessionMetric
-                .getCandidatePhasePerProviderMetric());
+        if (mProviders.get(componentName.flattenToString()) != null) {
+            ProviderSessionMetric providerSessionMetric =
+                    mProviders.get(componentName.flattenToString()).mProviderSessionMetric;
+            mRequestSessionMetric.collectChosenMetricViaCandidateTransfer(providerSessionMetric
+                    .getCandidatePhasePerProviderMetric());
+        }
         if (response != null) {
             mRequestSessionMetric.collectChosenProviderStatus(
                     ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode());
@@ -141,7 +145,9 @@
         } else {
             mRequestSessionMetric.collectChosenProviderStatus(
                     ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode());
-            respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREATE_OPTIONS,
+            String exception = CreateCredentialException.TYPE_NO_CREATE_OPTIONS;
+            mRequestSessionMetric.collectFrameworkException(exception);
+            respondToClientWithErrorAndFinish(exception,
                     "Invalid response");
         }
     }
@@ -154,18 +160,21 @@
 
     @Override
     public void onUiCancellation(boolean isUserCancellation) {
-        if (isUserCancellation) {
-            respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_USER_CANCELED,
-                    "User cancelled the selector");
-        } else {
-            respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_INTERRUPTED,
-                    "The UI was interrupted - please try again.");
+        String exception = CreateCredentialException.TYPE_USER_CANCELED;
+        String message = "User cancelled the selector";
+        if (!isUserCancellation) {
+            exception = CreateCredentialException.TYPE_INTERRUPTED;
+            message = "The UI was interrupted - please try again.";
         }
+        mRequestSessionMetric.collectFrameworkException(exception);
+        respondToClientWithErrorAndFinish(exception, message);
     }
 
     @Override
     public void onUiSelectorInvocationFailure() {
-        respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREATE_OPTIONS,
+        String exception = CreateCredentialException.TYPE_NO_CREATE_OPTIONS;
+        mRequestSessionMetric.collectFrameworkException(exception);
+        respondToClientWithErrorAndFinish(exception,
                 "No create options available.");
     }
 
@@ -181,7 +190,9 @@
                 Slog.i(TAG, "Provider status changed - ui invocation is needed");
                 getProviderDataAndInitiateUi();
             } else {
-                respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREATE_OPTIONS,
+                String exception = CreateCredentialException.TYPE_NO_CREATE_OPTIONS;
+                mRequestSessionMetric.collectFrameworkException(exception);
+                respondToClientWithErrorAndFinish(exception,
                         "No create options available.");
             }
         }
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index e9fa883..9eb3b3b 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -32,6 +32,7 @@
 import android.service.credentials.CallingAppInfo;
 import android.util.Slog;
 
+import com.android.server.credentials.metrics.ProviderSessionMetric;
 import com.android.server.credentials.metrics.ProviderStatusForMetrics;
 
 import java.util.ArrayList;
@@ -106,8 +107,10 @@
         } catch (RemoteException e) {
             mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false);
             mCredentialManagerUi.setStatus(CredentialManagerUi.UiStatus.TERMINATED);
+            String exception = GetCredentialException.TYPE_UNKNOWN;
+            mRequestSessionMetric.collectFrameworkException(exception);
             respondToClientWithErrorAndFinish(
-                    GetCredentialException.TYPE_UNKNOWN, "Unable to instantiate selector");
+                    exception, "Unable to instantiate selector");
         }
     }
 
@@ -128,9 +131,12 @@
             @Nullable GetCredentialResponse response) {
         Slog.i(TAG, "onFinalResponseReceived from: " + componentName.flattenToString());
         mRequestSessionMetric.collectUiResponseData(/*uiReturned=*/ true, System.nanoTime());
-        mRequestSessionMetric.collectChosenMetricViaCandidateTransfer(
-                mProviders.get(componentName.flattenToString())
-                        .mProviderSessionMetric.getCandidatePhasePerProviderMetric());
+        if (mProviders.get(componentName.flattenToString()) != null) {
+            ProviderSessionMetric providerSessionMetric =
+                    mProviders.get(componentName.flattenToString()).mProviderSessionMetric;
+            mRequestSessionMetric.collectChosenMetricViaCandidateTransfer(providerSessionMetric
+                    .getCandidatePhasePerProviderMetric());
+        }
         if (response != null) {
             mRequestSessionMetric.collectChosenProviderStatus(
                     ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode());
@@ -138,7 +144,9 @@
         } else {
             mRequestSessionMetric.collectChosenProviderStatus(
                     ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode());
-            respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL,
+            String exception = GetCredentialException.TYPE_NO_CREDENTIAL;
+            mRequestSessionMetric.collectFrameworkException(exception);
+            respondToClientWithErrorAndFinish(exception,
                     "Invalid response from provider");
         }
     }
@@ -152,18 +160,21 @@
 
     @Override
     public void onUiCancellation(boolean isUserCancellation) {
-        if (isUserCancellation) {
-            respondToClientWithErrorAndFinish(GetCredentialException.TYPE_USER_CANCELED,
-                    "User cancelled the selector");
-        } else {
-            respondToClientWithErrorAndFinish(GetCredentialException.TYPE_INTERRUPTED,
-                    "The UI was interrupted - please try again.");
+        String exception = GetCredentialException.TYPE_NO_CREDENTIAL;
+        String message = "User cancelled the selector";
+        if (!isUserCancellation) {
+            exception = GetCredentialException.TYPE_INTERRUPTED;
+            message = "The UI was interrupted - please try again.";
         }
+        mRequestSessionMetric.collectFrameworkException(exception);
+        respondToClientWithErrorAndFinish(exception, message);
     }
 
     @Override
     public void onUiSelectorInvocationFailure() {
-        respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL,
+        String exception = GetCredentialException.TYPE_NO_CREDENTIAL;
+        mRequestSessionMetric.collectFrameworkException(exception);
+        respondToClientWithErrorAndFinish(exception,
                 "No credentials available.");
     }
 
@@ -187,7 +198,9 @@
                 Slog.i(TAG, "Provider status changed - ui invocation is needed");
                 getProviderDataAndInitiateUi();
             } else {
-                respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL,
+                String exception = GetCredentialException.TYPE_NO_CREDENTIAL;
+                mRequestSessionMetric.collectFrameworkException(exception);
+                respondToClientWithErrorAndFinish(exception,
                         "No credentials available");
             }
         }
@@ -208,7 +221,9 @@
 
         // Respond to client if all auth entries are empty and nothing else to show on the UI
         if (providerDataContainsEmptyAuthEntriesOnly()) {
-            respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL,
+            String exception = GetCredentialException.TYPE_NO_CREDENTIAL;
+            mRequestSessionMetric.collectFrameworkException(exception);
+            respondToClientWithErrorAndFinish(exception,
                     "No credentials available");
         }
     }
diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
index e4c6b3a..f9c44a9 100644
--- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java
+++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
@@ -24,12 +24,14 @@
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.credentials.metrics.ApiName;
 import com.android.server.credentials.metrics.ApiStatus;
+import com.android.server.credentials.metrics.CandidateAggregateMetric;
 import com.android.server.credentials.metrics.CandidateBrowsingPhaseMetric;
 import com.android.server.credentials.metrics.CandidatePhaseMetric;
 import com.android.server.credentials.metrics.ChosenProviderFinalPhaseMetric;
 import com.android.server.credentials.metrics.EntryEnum;
 import com.android.server.credentials.metrics.InitialPhaseMetric;
 
+import java.security.SecureRandom;
 import java.util.List;
 import java.util.Map;
 
@@ -48,12 +50,15 @@
     public static final String DEFAULT_STRING = "";
     public static final int[] DEFAULT_REPEATED_INT_32 = new int[0];
     public static final String[] DEFAULT_REPEATED_STR = new String[0];
+    public static final boolean[] DEFAULT_REPEATED_BOOL = new boolean[0];
     // Used for single count metric emits, such as singular amounts of various types
     public static final int UNIT = 1;
     // Used for zero count metric emits, such as zero amounts of various types
     public static final int ZERO = 0;
     // The number of characters at the end of the string to use as a key
-    public static final int DELTA_CUT = 20;
+    public static final int DELTA_RESPONSES_CUT = 20;
+    // The cut for exception strings from the end - used to keep metrics small
+    public static final int DELTA_EXCEPTION_CUT = 30;
 
     /**
      * This retrieves the uid of any package name, given a context and a component name for the
@@ -76,6 +81,15 @@
     }
 
     /**
+     * Used to help generate random sequences for local sessions, in the time-scale of credential
+     * manager flows.
+     * @return a high entropy int useful to use in reasonable time-frame sessions.
+     */
+    public static int getHighlyUniqueInteger() {
+        return new SecureRandom().nextInt();
+    }
+
+    /**
      * Given any two timestamps in nanoseconds, this gets the difference and converts to
      * milliseconds. Assumes the difference is not larger than the maximum int size.
      *
@@ -127,7 +141,7 @@
                 index++;
             }
             FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_FINAL_PHASE_REPORTED,
-                    /* session_id */ finalPhaseMetric.getSessionId(),
+                    /* session_id */ finalPhaseMetric.getSessionIdProvider(),
                     /* sequence_num */ emitSequenceId,
                     /* ui_returned_final_start */ finalPhaseMetric.isUiReturned(),
                     /* chosen_provider_uid */ finalPhaseMetric.getChosenUid(),
@@ -165,8 +179,9 @@
                     finalPhaseMetric.getResponseCollective().getUniqueResponseStrings(),
                     /* per_classtype_counts */
                     finalPhaseMetric.getResponseCollective().getUniqueResponseCounts(),
-                    /* framework_exception_unique_classtypes */
-                    DEFAULT_STRING
+                    /* framework_exception_unique_classtype */
+                    finalPhaseMetric.getFrameworkException(),
+                    /* primary_indicated */ false
             );
         } catch (Exception e) {
             Slog.w(TAG, "Unexpected error during final provider uid emit: " + e);
@@ -210,7 +225,7 @@
                 CandidatePhaseMetric metric = session.mProviderSessionMetric
                         .getCandidatePhasePerProviderMetric();
                 if (sessionId == -1) {
-                    sessionId = metric.getSessionId();
+                    sessionId = metric.getSessionIdProvider();
                 }
                 if (!queryReturned) {
                     queryReturned = metric.isQueryReturned();
@@ -268,7 +283,9 @@
                     /* per_classtype_counts */
                     initialPhaseMetric.getUniqueRequestCounts(),
                     /* api_name */
-                    initialPhaseMetric.getApiName()
+                    initialPhaseMetric.getApiName(),
+                    /* primary_candidates_indicated */
+                    DEFAULT_REPEATED_BOOL
             );
         } catch (Exception e) {
             Slog.w(TAG, "Unexpected error during candidate provider uid metric emit: " + e);
@@ -312,7 +329,7 @@
             FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_INIT_PHASE_REPORTED,
                     /* api_name */ initialPhaseMetric.getApiName(),
                     /* caller_uid */ initialPhaseMetric.getCallerUid(),
-                    /* session_id */ initialPhaseMetric.getSessionId(),
+                    /* session_id */ initialPhaseMetric.getSessionIdCaller(),
                     /* sequence_num */ sequenceNum,
                     /* initial_timestamp_reference_nanoseconds */
                     initialPhaseMetric.getCredentialServiceStartedTimeNanoseconds(),
@@ -329,4 +346,129 @@
             Slog.w(TAG, "Unexpected error during initial metric emit: " + e);
         }
     }
+
+    /**
+     * A logging utility focused on track 1, where the calling app is known. This captures all
+     * aggregate information for the candidate phase.
+     *
+     * @param candidateAggregateMetric the aggregate candidate metric information collected
+     * @param sequenceNum the sequence number for this api call session emit
+     */
+    public static void logApiCalledAggregateCandidate(
+            CandidateAggregateMetric candidateAggregateMetric,
+            int sequenceNum) {
+        try {
+            if (!LOG_FLAG) {
+                FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_TOTAL_REPORTED,
+                        /*session_id*/ candidateAggregateMetric.getSessionIdProvider(),
+                        /*sequence_num*/ sequenceNum,
+                        /*query_returned*/ candidateAggregateMetric.isQueryReturned(),
+                        /*num_providers*/ candidateAggregateMetric.getNumProviders(),
+                        /*min_query_start_timestamp_microseconds*/
+                        DEFAULT_INT_32,
+                        /*max_query_end_timestamp_microseconds*/
+                        DEFAULT_INT_32,
+                        /*query_response_unique_classtypes*/
+                        DEFAULT_REPEATED_STR,
+                        /*query_per_classtype_counts*/
+                        DEFAULT_REPEATED_INT_32,
+                        /*query_unique_entries*/
+                        DEFAULT_REPEATED_INT_32,
+                        /*query_per_entry_counts*/
+                        DEFAULT_REPEATED_INT_32,
+                        /*query_total_candidate_failure*/
+                        DEFAULT_INT_32,
+                        /*query_framework_exception_unique_classtypes*/
+                        DEFAULT_REPEATED_STR,
+                        /*query_per_exception_classtype_counts*/
+                        DEFAULT_REPEATED_INT_32,
+                        /*auth_response_unique_classtypes*/
+                        DEFAULT_REPEATED_STR,
+                        /*auth_per_classtype_counts*/
+                        DEFAULT_REPEATED_INT_32,
+                        /*auth_unique_entries*/
+                        DEFAULT_REPEATED_INT_32,
+                        /*auth_per_entry_counts*/
+                        DEFAULT_REPEATED_INT_32,
+                        /*auth_total_candidate_failure*/
+                        DEFAULT_INT_32,
+                        /*auth_framework_exception_unique_classtypes*/
+                        DEFAULT_REPEATED_STR,
+                        /*auth_per_exception_classtype_counts*/
+                        DEFAULT_REPEATED_INT_32,
+                        /*num_auth_clicks*/
+                        DEFAULT_INT_32,
+                        /*auth_returned*/ false
+                );
+            }
+        } catch (Exception e) {
+            Slog.w(TAG, "Unexpected error during metric logging: " + e);
+        }
+    }
+
+    /**
+     * A logging utility used primarily for the final phase of the current metric setup for track 1.
+     *
+     * @param finalPhaseMetric     the coalesced data of the chosen provider
+     * @param browsingPhaseMetrics the coalesced data of the browsing phase
+     * @param apiStatus            the final status of this particular api call
+     * @param emitSequenceId       an emitted sequence id for the current session
+     */
+    public static void logApiCalledNoUidFinal(ChosenProviderFinalPhaseMetric finalPhaseMetric,
+            List<CandidateBrowsingPhaseMetric> browsingPhaseMetrics, int apiStatus,
+            int emitSequenceId) {
+        try {
+            if (!LOG_FLAG) {
+                return;
+            }
+            int browsedSize = browsingPhaseMetrics.size();
+            int[] browsedClickedEntries = new int[browsedSize];
+            int[] browsedProviderUid = new int[browsedSize];
+            int index = 0;
+            for (CandidateBrowsingPhaseMetric metric : browsingPhaseMetrics) {
+                browsedClickedEntries[index] = metric.getEntryEnum();
+                browsedProviderUid[index] = metric.getProviderUid();
+                index++;
+            }
+            FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_FINALNOUID_REPORTED,
+                    /* session_id */ finalPhaseMetric.getSessionIdCaller(),
+                    /* sequence_num */ emitSequenceId,
+                    /* ui_returned_final_start */ finalPhaseMetric.isUiReturned(),
+                    /* chosen_provider_query_start_timestamp_microseconds */
+                    finalPhaseMetric.getTimestampFromReferenceStartMicroseconds(finalPhaseMetric
+                            .getQueryStartTimeNanoseconds()),
+                    /* chosen_provider_query_end_timestamp_microseconds */
+                    finalPhaseMetric.getTimestampFromReferenceStartMicroseconds(finalPhaseMetric
+                            .getQueryEndTimeNanoseconds()),
+                    /* chosen_provider_ui_invoked_timestamp_microseconds */
+                    finalPhaseMetric.getTimestampFromReferenceStartMicroseconds(finalPhaseMetric
+                            .getUiCallStartTimeNanoseconds()),
+                    /* chosen_provider_ui_finished_timestamp_microseconds */
+                    finalPhaseMetric.getTimestampFromReferenceStartMicroseconds(finalPhaseMetric
+                            .getUiCallEndTimeNanoseconds()),
+                    /* chosen_provider_finished_timestamp_microseconds */
+                    finalPhaseMetric.getTimestampFromReferenceStartMicroseconds(finalPhaseMetric
+                            .getFinalFinishTimeNanoseconds()),
+                    /* chosen_provider_status */ finalPhaseMetric.getChosenProviderStatus(),
+                    /* chosen_provider_has_exception */ finalPhaseMetric.isHasException(),
+                    /* unique_entries */
+                    finalPhaseMetric.getResponseCollective().getUniqueEntries(),
+                    /* per_entry_counts */
+                    finalPhaseMetric.getResponseCollective().getUniqueEntryCounts(),
+                    /* unique_response_classtypes */
+                    finalPhaseMetric.getResponseCollective().getUniqueResponseStrings(),
+                    /* per_classtype_counts */
+                    finalPhaseMetric.getResponseCollective().getUniqueResponseCounts(),
+                    /* framework_exception_unique_classtype */
+                    finalPhaseMetric.getFrameworkException(),
+                    /* clicked_entries */ browsedClickedEntries,
+                    /* provider_of_clicked_entry */ browsedProviderUid,
+                    /* api_status */ apiStatus,
+                    /* primary_indicated */ false
+            );
+        } catch (Exception e) {
+            Slog.w(TAG, "Unexpected error during metric logging: " + e);
+        }
+    }
+
 }
diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
index 1f0346a..d4b8800 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
@@ -132,7 +132,8 @@
     protected void invokeSession() {
         if (mRemoteCredentialService != null) {
             startCandidateMetrics();
-            mRemoteCredentialService.onClearCredentialState(mProviderRequest, this);
+            mRemoteCredentialService.setCallback(this);
+            mRemoteCredentialService.onClearCredentialState(mProviderRequest);
         }
     }
 }
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index 16beaa4..25f20ca 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -193,11 +193,11 @@
         mProviderResponseDataHandler.addResponseContent(response.getCreateEntries(),
                 response.getRemoteCreateEntry());
         if (mProviderResponseDataHandler.isEmptyResponse(response)) {
-            mProviderSessionMetric.collectCandidateEntryMetrics(response);
+            mProviderSessionMetric.collectCandidateEntryMetrics(response, /*isAuthEntry*/false);
             updateStatusAndInvokeCallback(Status.EMPTY_RESPONSE,
                     /*source=*/ CredentialsSource.REMOTE_PROVIDER);
         } else {
-            mProviderSessionMetric.collectCandidateEntryMetrics(response);
+            mProviderSessionMetric.collectCandidateEntryMetrics(response, /*isAuthEntry*/false);
             updateStatusAndInvokeCallback(Status.SAVE_ENTRIES_RECEIVED,
                     /*source=*/ CredentialsSource.REMOTE_PROVIDER);
         }
@@ -248,7 +248,8 @@
     protected void invokeSession() {
         if (mRemoteCredentialService != null) {
             startCandidateMetrics();
-            mRemoteCredentialService.onBeginCreateCredential(mProviderRequest, this);
+            mRemoteCredentialService.setCallback(this);
+            mRemoteCredentialService.onBeginCreateCredential(mProviderRequest);
         }
     }
 
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index 1e80703..51af25b 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -309,7 +309,8 @@
     protected void invokeSession() {
         if (mRemoteCredentialService != null) {
             startCandidateMetrics();
-            mRemoteCredentialService.onBeginGetCredential(mProviderRequest, this);
+            mRemoteCredentialService.setCallback(this);
+            mRemoteCredentialService.onBeginGetCredential(mProviderRequest);
         }
     }
 
@@ -432,6 +433,7 @@
         BeginGetCredentialResponse response = PendingIntentResultHandler
                 .extractResponseContent(providerPendingIntentResponse
                         .getResultData());
+        mProviderSessionMetric.collectCandidateEntryMetrics(response, /*isAuthEntry*/true);
         if (response != null && !mProviderResponseDataHandler.isEmptyResponse(response)) {
             addToInitialRemoteResponse(response, /*isInitialResponse=*/ false);
             // Additional content received is in the form of new response content.
@@ -469,12 +471,12 @@
         addToInitialRemoteResponse(response, /*isInitialResponse=*/true);
         // Log the data.
         if (mProviderResponseDataHandler.isEmptyResponse(response)) {
-            mProviderSessionMetric.collectCandidateEntryMetrics(response);
+            mProviderSessionMetric.collectCandidateEntryMetrics(response, /*isAuthEntry*/false);
             updateStatusAndInvokeCallback(Status.EMPTY_RESPONSE,
                     /*source=*/ CredentialsSource.REMOTE_PROVIDER);
             return;
         }
-        mProviderSessionMetric.collectCandidateEntryMetrics(response);
+        mProviderSessionMetric.collectCandidateEntryMetrics(response, /*isAuthEntry*/false);
         updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED,
                 /*source=*/ CredentialsSource.REMOTE_PROVIDER);
     }
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index 27b78a4..068ca79 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -71,7 +71,7 @@
     @NonNull
     protected Boolean mProviderResponseSet = false;
     @NonNull
-    protected final ProviderSessionMetric mProviderSessionMetric = new ProviderSessionMetric();
+    protected final ProviderSessionMetric mProviderSessionMetric;
     @NonNull
     private int mProviderSessionUid;
 
@@ -114,6 +114,13 @@
     }
 
     /**
+     * Gives access to the objects metric collectors.
+     */
+    public ProviderSessionMetric getProviderSessionMetric() {
+        return this.mProviderSessionMetric;
+    }
+
+    /**
      * Interface to be implemented by any class that wishes to get a callback when a particular
      * provider session's status changes. Typically, implemented by the {@link RequestSession}
      * class.
@@ -147,6 +154,8 @@
         mComponentName = componentName;
         mRemoteCredentialService = remoteCredentialService;
         mProviderSessionUid = MetricUtilities.getPackageUid(mContext, mComponentName);
+        mProviderSessionMetric = new ProviderSessionMetric(
+                ((RequestSession) mCallbacks).mRequestSessionMetric.getSessionIdTrackTwo());
     }
 
     /** Provider status at various states of the provider session. */
@@ -206,10 +215,10 @@
             CredentialsSource source) {
         setStatus(status);
         mProviderSessionMetric.collectCandidateMetricUpdate(isTerminatingStatus(status),
-                isCompletionStatus(status), mProviderSessionUid);
+                isCompletionStatus(status), mProviderSessionUid,
+                source == CredentialsSource.AUTH_ENTRY);
         mCallbacks.onProviderStatusChanged(status, mComponentName, source);
     }
-
     /** Common method that transfers metrics from the init phase to candidates */
     protected void startCandidateMetrics() {
         mProviderSessionMetric.collectCandidateMetricSetupViaInitialMetric(
diff --git a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
index 83e7e93..4bcf8be 100644
--- a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
+++ b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
@@ -48,6 +48,7 @@
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 
 /**
@@ -66,6 +67,10 @@
 
     private final ComponentName mComponentName;
 
+    private AtomicBoolean mOngoingRequest = new AtomicBoolean(false);
+
+    @Nullable private ProviderCallbacks mCallback;
+
     /**
      * Callbacks to be invoked when the provider remote service responds with a
      * success or failure.
@@ -94,12 +99,35 @@
         mComponentName = componentName;
     }
 
+    public void setCallback(ProviderCallbacks callback) {
+        mCallback = callback;
+    }
+
     /** Unbinds automatically after this amount of time. */
     @Override
     protected long getAutoDisconnectTimeoutMs() {
         return TIMEOUT_IDLE_SERVICE_CONNECTION_MILLIS;
     }
 
+    @Override
+    public void onBindingDied(ComponentName name) {
+        super.onBindingDied(name);
+
+        Slog.w(TAG, "binding died for: " + name);
+    }
+
+    @Override
+    public void binderDied() {
+        super.binderDied();
+        Slog.w(TAG, "binderDied");
+
+        if (mCallback != null) {
+            mOngoingRequest.set(false);
+            mCallback.onProviderServiceDied(this);
+        }
+
+    }
+
     /** Return the componentName of the service to be connected. */
     @NonNull
     public ComponentName getComponentName() {
@@ -116,11 +144,14 @@
      * provider service.
      *
      * @param request  the request to be sent to the provider
-     * @param callback the callback to be used to send back the provider response to the
-     *                 {@link ProviderGetSession} class that maintains provider state
      */
-    public void onBeginGetCredential(@NonNull BeginGetCredentialRequest request,
-            ProviderCallbacks<BeginGetCredentialResponse> callback) {
+    public void onBeginGetCredential(@NonNull BeginGetCredentialRequest request) {
+        if (mCallback == null) {
+            Slog.w(TAG, "Callback is not set");
+            return;
+        }
+        mOngoingRequest.set(true);
+
         AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
         AtomicReference<CompletableFuture<BeginGetCredentialResponse>> futureRef =
                 new AtomicReference<>();
@@ -154,7 +185,9 @@
                                     dispatchCancellationSignal(cancellation);
                                 } else {
                                     cancellationSink.set(cancellation);
-                                    callback.onProviderCancellable(cancellation);
+                                    if (mCallback != null) {
+                                        mCallback.onProviderCancellable(cancellation);
+                                    }
                                 }
                             }
                         });
@@ -166,7 +199,7 @@
         futureRef.set(connectThenExecute);
 
         connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() ->
-                handleExecutionResponse(result, error, cancellationSink, callback)));
+                handleExecutionResponse(result, error, cancellationSink)));
     }
 
     /**
@@ -174,11 +207,14 @@
      * provider service.
      *
      * @param request  the request to be sent to the provider
-     * @param callback the callback to be used to send back the provider response to the
-     *                 {@link ProviderCreateSession} class that maintains provider state
      */
-    public void onBeginCreateCredential(@NonNull BeginCreateCredentialRequest request,
-            ProviderCallbacks<BeginCreateCredentialResponse> callback) {
+    public void onBeginCreateCredential(@NonNull BeginCreateCredentialRequest request) {
+        if (mCallback == null) {
+            Slog.w(TAG, "Callback is not set");
+            return;
+        }
+        mOngoingRequest.set(true);
+
         AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
         AtomicReference<CompletableFuture<BeginCreateCredentialResponse>> futureRef =
                 new AtomicReference<>();
@@ -212,7 +248,9 @@
                                             dispatchCancellationSignal(cancellation);
                                         } else {
                                             cancellationSink.set(cancellation);
-                                            callback.onProviderCancellable(cancellation);
+                                            if (mCallback != null) {
+                                                mCallback.onProviderCancellable(cancellation);
+                                            }
                                         }
                                     }
                                 });
@@ -224,7 +262,7 @@
         futureRef.set(connectThenExecute);
 
         connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() ->
-                handleExecutionResponse(result, error, cancellationSink, callback)));
+                handleExecutionResponse(result, error, cancellationSink)));
     }
 
     /**
@@ -232,11 +270,14 @@
      * provider service.
      *
      * @param request  the request to be sent to the provider
-     * @param callback the callback to be used to send back the provider response to the
-     *                 {@link ProviderClearSession} class that maintains provider state
      */
-    public void onClearCredentialState(@NonNull ClearCredentialStateRequest request,
-            ProviderCallbacks<Void> callback) {
+    public void onClearCredentialState(@NonNull ClearCredentialStateRequest request) {
+        if (mCallback == null) {
+            Slog.w(TAG, "Callback is not set");
+            return;
+        }
+        mOngoingRequest.set(true);
+
         AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
         AtomicReference<CompletableFuture<Void>> futureRef = new AtomicReference<>();
 
@@ -269,7 +310,9 @@
                                             dispatchCancellationSignal(cancellation);
                                         } else {
                                             cancellationSink.set(cancellation);
-                                            callback.onProviderCancellable(cancellation);
+                                            if (mCallback != null) {
+                                                mCallback.onProviderCancellable(cancellation);
+                                            }
                                         }
                                     }
                                 });
@@ -281,40 +324,58 @@
         futureRef.set(connectThenExecute);
 
         connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() ->
-                handleExecutionResponse(result, error, cancellationSink, callback)));
+                handleExecutionResponse(result, error, cancellationSink)));
     }
 
     private <T> void handleExecutionResponse(T result,
             Throwable error,
-            AtomicReference<ICancellationSignal> cancellationSink,
-            ProviderCallbacks<T> callback) {
+            AtomicReference<ICancellationSignal> cancellationSink) {
         if (error == null) {
-            callback.onProviderResponseSuccess(result);
+            if (mCallback != null) {
+                mCallback.onProviderResponseSuccess(result);
+            }
         } else {
             if (error instanceof TimeoutException) {
                 Slog.i(TAG, "Remote provider response timed tuo for: " + mComponentName);
+                if (!mOngoingRequest.get()) {
+                    return;
+                }
                 dispatchCancellationSignal(cancellationSink.get());
-                callback.onProviderResponseFailure(
-                        CredentialProviderErrors.ERROR_TIMEOUT,
-                        null);
+                if (mCallback != null) {
+                    mOngoingRequest.set(false);
+                    mCallback.onProviderResponseFailure(
+                            CredentialProviderErrors.ERROR_TIMEOUT, null);
+                }
             } else if (error instanceof CancellationException) {
                 Slog.i(TAG, "Cancellation exception for remote provider: " + mComponentName);
+                if (!mOngoingRequest.get()) {
+                    return;
+                }
                 dispatchCancellationSignal(cancellationSink.get());
-                callback.onProviderResponseFailure(
-                        CredentialProviderErrors.ERROR_TASK_CANCELED,
-                        null);
+                if (mCallback != null) {
+                    mOngoingRequest.set(false);
+                    mCallback.onProviderResponseFailure(
+                            CredentialProviderErrors.ERROR_TASK_CANCELED,
+                            null);
+                }
             } else if (error instanceof GetCredentialException) {
-                callback.onProviderResponseFailure(
-                        CredentialProviderErrors.ERROR_PROVIDER_FAILURE,
-                        (GetCredentialException) error);
+                if (mCallback != null) {
+                    mCallback.onProviderResponseFailure(
+                            CredentialProviderErrors.ERROR_PROVIDER_FAILURE,
+                            (GetCredentialException) error);
+                }
             } else if (error instanceof CreateCredentialException) {
-                callback.onProviderResponseFailure(
-                        CredentialProviderErrors.ERROR_PROVIDER_FAILURE,
-                        (CreateCredentialException) error);
+                if (mCallback != null) {
+                    mCallback.onProviderResponseFailure(
+                            CredentialProviderErrors.ERROR_PROVIDER_FAILURE,
+                            (CreateCredentialException) error);
+                }
             } else {
-                callback.onProviderResponseFailure(
-                        CredentialProviderErrors.ERROR_UNKNOWN,
-                        (Exception) error);
+                if (mCallback != null) {
+                    mCallback.onProviderResponseFailure(
+                            CredentialProviderErrors.ERROR_UNKNOWN,
+                            (Exception) error);
+                }
             }
         }
     }
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index 7caa921..a41b571 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -75,6 +75,8 @@
     protected final Handler mHandler;
     @UserIdInt
     protected final int mUserId;
+
+    protected final int mUniqueSessionInteger;
     private final int mCallingUid;
     @NonNull
     protected final CallingAppInfo mClientAppInfo;
@@ -82,7 +84,7 @@
     protected final CancellationSignal mCancellationSignal;
 
     protected final Map<String, ProviderSession> mProviders = new ConcurrentHashMap<>();
-    protected final RequestSessionMetric mRequestSessionMetric = new RequestSessionMetric();
+    protected final RequestSessionMetric mRequestSessionMetric;
     protected final String mHybridService;
 
     protected final Object mLock;
@@ -132,7 +134,10 @@
                 mUserId, this, mEnabledProviders);
         mHybridService = context.getResources().getString(
                 R.string.config_defaultCredentialManagerHybridService);
-        mRequestSessionMetric.collectInitialPhaseMetricInfo(timestampStarted, mRequestId,
+        mUniqueSessionInteger = MetricUtilities.getHighlyUniqueInteger();
+        mRequestSessionMetric = new RequestSessionMetric(mUniqueSessionInteger,
+                MetricUtilities.getHighlyUniqueInteger());
+        mRequestSessionMetric.collectInitialPhaseMetricInfo(timestampStarted,
                 mCallingUid, ApiName.getMetricCodeFromRequestInfo(mRequestType));
         setCancellationListener();
     }
diff --git a/services/credentials/java/com/android/server/credentials/metrics/BrowsedAuthenticationMetric.java b/services/credentials/java/com/android/server/credentials/metrics/BrowsedAuthenticationMetric.java
new file mode 100644
index 0000000..51e86d5
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/metrics/BrowsedAuthenticationMetric.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.credentials.metrics;
+
+/**
+ * Encapsulates an authentication entry click atom, as a part of track 2.
+ * Contains information about what was collected from the authentication entry output.
+ */
+public class BrowsedAuthenticationMetric {
+    // The session id of this provider known flow related metric
+    private final int mSessionIdProvider;
+    // TODO(b/271135048) - Match the atom and provide a clean per provider session metric
+    // encapsulation.
+
+    public BrowsedAuthenticationMetric(int sessionIdProvider) {
+        mSessionIdProvider = sessionIdProvider;
+    }
+
+    public int getSessionIdProvider() {
+        return mSessionIdProvider;
+    }
+}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidateAggregateMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidateAggregateMetric.java
new file mode 100644
index 0000000..08e7583
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/metrics/CandidateAggregateMetric.java
@@ -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.server.credentials.metrics;
+
+import com.android.server.credentials.ProviderSession;
+
+import java.util.Map;
+
+/**
+ * This will generate most of its data via using the information of {@link CandidatePhaseMetric}
+ * across all the providers. This belongs to the metric flow where the calling app is known.
+ */
+public class CandidateAggregateMetric {
+
+    private static final String TAG = "CandidateProviderMetric";
+    // The session id of this provider metric
+    private final int mSessionIdProvider;
+    // Indicates if this provider returned from the query phase, default false
+    private boolean mQueryReturned = false;
+    // Indicates the total number of providers this aggregate captures information for, default 0
+    private int mNumProviders = 0;
+    // Indicates the total number of authentication entries that were tapped in aggregate, default 0
+    private int mNumAuthEntriesTapped = 0;
+
+    public CandidateAggregateMetric(int sessionIdTrackOne) {
+        mSessionIdProvider = sessionIdTrackOne;
+    }
+
+    public int getSessionIdProvider() {
+        return mSessionIdProvider;
+    }
+
+    /**
+     * This will take all the candidate data captured and aggregate that information.
+     * TODO(b/271135048) : Add on authentication entry outputs from track 2 here as well once
+     * generated
+     * @param providers the providers associated with the candidate flow
+     */
+    public void collectAverages(Map<String, ProviderSession> providers) {
+        // TODO(b/271135048) : Complete this method
+        mNumProviders = providers.size();
+        var providerSessions = providers.values();
+        for (var session : providerSessions) {
+            var metric = session.getProviderSessionMetric();
+            mQueryReturned = mQueryReturned || metric
+                    .mCandidatePhasePerProviderMetric.isQueryReturned();
+        }
+    }
+
+    public int getNumProviders() {
+        return mNumProviders;
+    }
+
+    public boolean isQueryReturned() {
+        return mQueryReturned;
+    }
+
+    public int getNumAuthEntriesTapped() {
+        return mNumAuthEntriesTapped;
+    }
+}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java
index 07af654..6b74252 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java
@@ -27,23 +27,11 @@
  * though collection will begin in the candidate phase when the user begins browsing options.
  */
 public class CandidateBrowsingPhaseMetric {
-    // The session id associated with the API Call this candidate provider is a part of, default -1
-    private int mSessionId = -1;
     // The EntryEnum that was pressed, defaults to -1
     private int mEntryEnum = EntryEnum.UNKNOWN.getMetricCode();
     // The provider associated with the press, defaults to -1
     private int mProviderUid = -1;
 
-    /* -- The session ID -- */
-
-    public void setSessionId(int sessionId) {
-        mSessionId = sessionId;
-    }
-
-    public int getSessionId() {
-        return mSessionId;
-    }
-
     /* -- The Entry of this tap -- */
 
     public void setEntryEnum(int entryEnum) {
diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java
index 3ea9b1ce..d9bf4a1 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/CandidatePhaseMetric.java
@@ -32,8 +32,8 @@
 public class CandidatePhaseMetric {
 
     private static final String TAG = "CandidateProviderMetric";
-    // The session id of this provider, default set to -1
-    private int mSessionId = -1;
+    // The session id of this provider metric
+    private final int mSessionIdProvider;
     // Indicates if this provider returned from the query phase, default false
     private boolean mQueryReturned = false;
 
@@ -53,13 +53,15 @@
     private int mProviderQueryStatus = -1;
     // Indicates if an exception was thrown by this provider, false by default
     private boolean mHasException = false;
+    // Indicates the framework only exception belonging to this provider
     private String mFrameworkException = "";
 
     // Stores the response credential information, as well as the response entry information which
     // by default, contains empty info
     private ResponseCollective mResponseCollective = new ResponseCollective(Map.of(), Map.of());
 
-    public CandidatePhaseMetric() {
+    public CandidatePhaseMetric(int sessionIdTrackTwo) {
+        mSessionIdProvider = sessionIdTrackTwo;
     }
 
     /* ---------- Latencies ---------- */
@@ -141,12 +143,8 @@
 
     /* -------------- Session Id ---------------- */
 
-    public void setSessionId(int sessionId) {
-        mSessionId = sessionId;
-    }
-
-    public int getSessionId() {
-        return mSessionId;
+    public int getSessionIdProvider() {
+        return mSessionIdProvider;
     }
 
     /* -------------- Query Returned Status ---------------- */
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java
index 93a8290..e8af860 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderFinalPhaseMetric.java
@@ -32,8 +32,12 @@
  */
 public class ChosenProviderFinalPhaseMetric {
     private static final String TAG = "ChosenFinalPhaseMetric";
-    // The session id associated with this API call, used to unite split emits
-    private int mSessionId = -1;
+    // The session id associated with this API call, used to unite split emits, for the flow
+    // where we know the calling app
+    private final int mSessionIdCaller;
+    // The session id associated with this API call, used to unite split emits, for the flow
+    // where we know the provider apps
+    private final int mSessionIdProvider;
     // Reveals if the UI was returned, false by default
     private boolean mUiReturned = false;
     private int mChosenUid = -1;
@@ -66,13 +70,17 @@
     private int mChosenProviderStatus = -1;
     // Indicates if an exception was thrown by this provider, false by default
     private boolean mHasException = false;
+    // Indicates a framework only exception that occurs in the final phase of the flow
+    private String mFrameworkException = "";
 
     // Stores the response credential information, as well as the response entry information which
     // by default, contains empty info
     private ResponseCollective mResponseCollective = new ResponseCollective(Map.of(), Map.of());
 
 
-    public ChosenProviderFinalPhaseMetric() {
+    public ChosenProviderFinalPhaseMetric(int sessionIdCaller, int sessionIdProvider) {
+        mSessionIdCaller = sessionIdCaller;
+        mSessionIdProvider = sessionIdProvider;
     }
 
     /* ------------------- UID ------------------- */
@@ -235,12 +243,8 @@
 
     /* ----------- Session ID -------------- */
 
-    public void setSessionId(int sessionId) {
-        mSessionId = sessionId;
-    }
-
-    public int getSessionId() {
-        return mSessionId;
+    public int getSessionIdProvider() {
+        return mSessionIdProvider;
     }
 
     /* ----------- UI Returned Successfully -------------- */
@@ -272,4 +276,20 @@
     public ResponseCollective getResponseCollective() {
         return mResponseCollective;
     }
+
+    /* -------------- Framework Exception ---------------- */
+
+    public void setFrameworkException(String frameworkException) {
+        mFrameworkException = frameworkException;
+    }
+
+    public String getFrameworkException() {
+        return mFrameworkException;
+    }
+
+    /* -------------- Session ID for Track One (Known Calling App) ---------------- */
+
+    public int getSessionIdCaller() {
+        return mSessionIdCaller;
+    }
 }
diff --git a/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
index 060e56c..8e965e3 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
@@ -32,8 +32,8 @@
     private int mApiName = ApiName.UNKNOWN.getMetricCode();
     // The caller uid of the calling application, default to -1
     private int mCallerUid = -1;
-    // The session id to unite multiple atom emits, default to -1
-    private int mSessionId = -1;
+    // The session id to unite multiple atom emits
+    private final int mSessionIdCaller;
 
     // Raw timestamps in nanoseconds, *the only* one logged as such (i.e. 64 bits) since it is a
     // reference point.
@@ -50,7 +50,8 @@
     private Map<String, Integer> mRequestCounts = new LinkedHashMap<>();
 
 
-    public InitialPhaseMetric() {
+    public InitialPhaseMetric(int sessionIdTrackOne) {
+        mSessionIdCaller = sessionIdTrackOne;
     }
 
     /* ---------- Latencies ---------- */
@@ -105,12 +106,8 @@
 
     /* ------ SessionId ------ */
 
-    public void setSessionId(int sessionId) {
-        mSessionId = sessionId;
-    }
-
-    public int getSessionId() {
-        return mSessionId;
+    public int getSessionIdCaller() {
+        return mSessionIdCaller;
     }
 
     /* ------ Count Request Class Types ------ */
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java b/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java
index f011b55..47db8f5 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/ProviderSessionMetric.java
@@ -16,7 +16,7 @@
 
 package com.android.server.credentials.metrics;
 
-import static com.android.server.credentials.MetricUtilities.DELTA_CUT;
+import static com.android.server.credentials.MetricUtilities.DELTA_RESPONSES_CUT;
 import static com.android.server.credentials.MetricUtilities.generateMetricKey;
 
 import android.annotation.NonNull;
@@ -44,10 +44,17 @@
 
     // Specific candidate provider metric for the provider this session handles
     @NonNull
-    protected final CandidatePhaseMetric mCandidatePhasePerProviderMetric =
-            new CandidatePhaseMetric();
+    protected final CandidatePhaseMetric mCandidatePhasePerProviderMetric;
 
-    public ProviderSessionMetric() {}
+    // IFF there was an authentication entry clicked, this stores all required information for
+    // that event. This is for the 'get' flow.
+    @NonNull
+    protected final BrowsedAuthenticationMetric mBrowsedAuthenticationMetric;
+
+    public ProviderSessionMetric(int sessionIdTrackTwo) {
+        mCandidatePhasePerProviderMetric = new CandidatePhaseMetric(sessionIdTrackTwo);
+        mBrowsedAuthenticationMetric = new BrowsedAuthenticationMetric(sessionIdTrackTwo);
+    }
 
     /**
      * Retrieve the candidate provider phase metric and the data it contains.
@@ -56,6 +63,7 @@
         return mCandidatePhasePerProviderMetric;
     }
 
+
     /**
      * This collects for ProviderSessions, with respect to the candidate providers, whether
      * an exception occurred in the candidate call.
@@ -78,6 +86,13 @@
         }
     }
 
+    private void collectAuthEntryUpdate(boolean isFailureStatus,
+            boolean isCompletionStatus, int providerSessionUid) {
+        // TODO(b/271135048) - Mimic typical candidate update, but with authentication metric
+        // Collect the final timestamps (and start timestamp), status, exceptions and the provider
+        // uid. This occurs typically *after* the collection is complete.
+    }
+
     /**
      * Used to collect metrics at the update stage when a candidate provider gives back an update.
      *
@@ -86,8 +101,12 @@
      * @param providerSessionUid the uid of the provider
      */
     public void collectCandidateMetricUpdate(boolean isFailureStatus,
-            boolean isCompletionStatus, int providerSessionUid) {
+            boolean isCompletionStatus, int providerSessionUid, boolean isAuthEntry) {
         try {
+            if (isAuthEntry) {
+                collectAuthEntryUpdate(isFailureStatus, isCompletionStatus, providerSessionUid);
+                return;
+            }
             mCandidatePhasePerProviderMetric.setCandidateUid(providerSessionUid);
             mCandidatePhasePerProviderMetric
                     .setQueryFinishTimeNanoseconds(System.nanoTime());
@@ -119,7 +138,6 @@
      */
     public void collectCandidateMetricSetupViaInitialMetric(InitialPhaseMetric initMetric) {
         try {
-            mCandidatePhasePerProviderMetric.setSessionId(initMetric.getSessionId());
             mCandidatePhasePerProviderMetric.setServiceBeganTimeNanoseconds(
                     initMetric.getCredentialServiceStartedTimeNanoseconds());
             mCandidatePhasePerProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime());
@@ -133,13 +151,14 @@
      * purposes.
      *
      * @param response contains entries and data from the candidate provider responses
+     * @param isAuthEntry indicates if this is an auth entry collection or not
      * @param <R> the response type associated with the API flow in progress
      */
-    public <R> void collectCandidateEntryMetrics(R response) {
+    public <R> void collectCandidateEntryMetrics(R response, boolean isAuthEntry) {
         try {
             if (response instanceof BeginGetCredentialResponse) {
                 beginGetCredentialResponseCollectionCandidateEntryMetrics(
-                        (BeginGetCredentialResponse) response);
+                        (BeginGetCredentialResponse) response, isAuthEntry);
             } else if (response instanceof BeginCreateCredentialResponse) {
                 beginCreateCredentialResponseCollectionCandidateEntryMetrics(
                         (BeginCreateCredentialResponse) response);
@@ -170,7 +189,7 @@
         entryCounts.put(EntryEnum.AUTHENTICATION_ENTRY, numAuthEntries);
 
         entries.forEach(entry -> {
-            String entryKey = generateMetricKey(entry.getType(), DELTA_CUT);
+            String entryKey = generateMetricKey(entry.getType(), DELTA_RESPONSES_CUT);
             responseCounts.put(entryKey, responseCounts.getOrDefault(entryKey, 0) + 1);
         });
 
@@ -198,7 +217,7 @@
     }
 
     private void beginGetCredentialResponseCollectionCandidateEntryMetrics(
-            BeginGetCredentialResponse response) {
+            BeginGetCredentialResponse response, boolean isAuthEntry) {
         Map<EntryEnum, Integer> entryCounts = new LinkedHashMap<>();
         Map<String, Integer> responseCounts = new LinkedHashMap<>();
         int numCredEntries = response.getCredentialEntries().size();
@@ -212,11 +231,16 @@
         entryCounts.put(EntryEnum.AUTHENTICATION_ENTRY, numAuthEntries);
 
         response.getCredentialEntries().forEach(entry -> {
-            String entryKey = generateMetricKey(entry.getType(), DELTA_CUT);
+            String entryKey = generateMetricKey(entry.getType(), DELTA_RESPONSES_CUT);
             responseCounts.put(entryKey, responseCounts.getOrDefault(entryKey, 0) + 1);
         });
 
         ResponseCollective responseCollective = new ResponseCollective(responseCounts, entryCounts);
-        mCandidatePhasePerProviderMetric.setResponseCollective(responseCollective);
+
+        if (!isAuthEntry) {
+            mCandidatePhasePerProviderMetric.setResponseCollective(responseCollective);
+        } else {
+            // TODO(b/immediately) - Add the auth entry get logic
+        }
     }
 }
diff --git a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java
index 4624e0b..03ffe23 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java
@@ -16,14 +16,16 @@
 
 package com.android.server.credentials.metrics;
 
-import static com.android.server.credentials.MetricUtilities.DELTA_CUT;
+import static com.android.server.credentials.MetricUtilities.DELTA_EXCEPTION_CUT;
+import static com.android.server.credentials.MetricUtilities.DELTA_RESPONSES_CUT;
 import static com.android.server.credentials.MetricUtilities.generateMetricKey;
 import static com.android.server.credentials.MetricUtilities.logApiCalledCandidatePhase;
 import static com.android.server.credentials.MetricUtilities.logApiCalledFinalPhase;
+import static com.android.server.credentials.MetricUtilities.logApiCalledNoUidFinal;
 
+import android.annotation.NonNull;
 import android.credentials.GetCredentialRequest;
 import android.credentials.ui.UserSelectionDialogResult;
-import android.os.IBinder;
 import android.util.Slog;
 
 import com.android.server.credentials.ProviderSession;
@@ -47,13 +49,24 @@
     // As emits occur in sequential order, increment this counter and utilize
     protected int mSequenceCounter = 0;
 
-    protected final InitialPhaseMetric mInitialPhaseMetric = new InitialPhaseMetric();
+    protected final InitialPhaseMetric mInitialPhaseMetric;
     protected final ChosenProviderFinalPhaseMetric
-            mChosenProviderFinalPhaseMetric = new ChosenProviderFinalPhaseMetric();
+            mChosenProviderFinalPhaseMetric;
     // TODO(b/271135048) - Replace this with a new atom per each browsing emit (V4)
     protected List<CandidateBrowsingPhaseMetric> mCandidateBrowsingPhaseMetric = new ArrayList<>();
+    // Specific aggregate candidate provider metric for the provider this session handles
+    @NonNull
+    protected final CandidateAggregateMetric mCandidateAggregateMetric;
+    // Since track two is shared, this allows provider sessions to capture a metric-specific
+    // session token for the flow where the provider is known
+    private final int mSessionIdTrackTwo;
 
-    public RequestSessionMetric() {
+    public RequestSessionMetric(int sessionIdTrackOne, int sessionIdTrackTwo) {
+        mSessionIdTrackTwo = sessionIdTrackTwo;
+        mInitialPhaseMetric = new InitialPhaseMetric(sessionIdTrackOne);
+        mCandidateAggregateMetric = new CandidateAggregateMetric(sessionIdTrackOne);
+        mChosenProviderFinalPhaseMetric = new ChosenProviderFinalPhaseMetric(
+                sessionIdTrackOne, sessionIdTrackTwo);
     }
 
     /**
@@ -75,18 +88,25 @@
     }
 
     /**
+     * @return the aggregate candidate phase metrics associated with the request session
+     */
+    public CandidateAggregateMetric getCandidateAggregateMetric() {
+        return mCandidateAggregateMetric;
+    }
+
+    /**
      * Upon starting the service, this fills the initial phase metric properly.
      *
      * @param timestampStarted the timestamp the service begins at
-     * @param mRequestId       the IBinder used to retrieve a unique id
      * @param mCallingUid      the calling process's uid
      * @param metricCode       typically pulled from {@link ApiName}
+     * @param callingAppFlowUniqueInt the unique integer used as the session id for the calling app
+     *                                known flow
      */
-    public void collectInitialPhaseMetricInfo(long timestampStarted, IBinder mRequestId,
+    public void collectInitialPhaseMetricInfo(long timestampStarted,
             int mCallingUid, int metricCode) {
         try {
             mInitialPhaseMetric.setCredentialServiceStartedTimeNanoseconds(timestampStarted);
-            mInitialPhaseMetric.setSessionId(mRequestId.hashCode());
             mInitialPhaseMetric.setCallerUid(mCallingUid);
             mInitialPhaseMetric.setApiName(metricCode);
         } catch (Exception e) {
@@ -168,11 +188,9 @@
         Map<String, Integer> uniqueRequestCounts = new LinkedHashMap<>();
         try {
             request.getCredentialOptions().forEach(option -> {
-                String optionKey = generateMetricKey(option.getType(), DELTA_CUT);
-                if (!uniqueRequestCounts.containsKey(optionKey)) {
-                    uniqueRequestCounts.put(optionKey, 0);
-                }
-                uniqueRequestCounts.put(optionKey, uniqueRequestCounts.get(optionKey) + 1);
+                String optionKey = generateMetricKey(option.getType(), DELTA_RESPONSES_CUT);
+                uniqueRequestCounts.put(optionKey, uniqueRequestCounts.getOrDefault(optionKey,
+                        0) + 1);
             });
         } catch (Exception e) {
             Slog.i(TAG, "Unexpected error during get request metric logging: " + e);
@@ -207,7 +225,6 @@
             CandidatePhaseMetric selectedProviderPhaseMetric) {
         try {
             CandidateBrowsingPhaseMetric browsingPhaseMetric = new CandidateBrowsingPhaseMetric();
-            browsingPhaseMetric.setSessionId(mInitialPhaseMetric.getSessionId());
             browsingPhaseMetric.setEntryEnum(
                     EntryEnum.getMetricCodeFromString(selection.getEntryKey()));
             browsingPhaseMetric.setProviderUid(selectedProviderPhaseMetric.getCandidateUid());
@@ -218,7 +235,7 @@
     }
 
     /**
-     * Updates the final phase metric with the designated bit
+     * Updates the final phase metric with the designated bit.
      *
      * @param exceptionBitFinalPhase represents if the final phase provider had an exception
      */
@@ -231,6 +248,21 @@
     }
 
     /**
+     * This allows collecting the framework exception string for the final phase metric.
+     * NOTE that this exception will be cut for space optimizations.
+     *
+     * @param exception the framework exception that is being recorded
+     */
+    public void collectFrameworkException(String exception) {
+        try {
+            mChosenProviderFinalPhaseMetric.setFrameworkException(
+                    generateMetricKey(exception, DELTA_EXCEPTION_CUT));
+        } catch (Exception e) {
+            Slog.w(TAG, "Unexpected error during metric logging: " + e);
+        }
+    }
+
+    /**
      * Allows encapsulating the overall final phase metric status from the chosen and final
      * provider.
      *
@@ -260,7 +292,6 @@
      */
     public void collectChosenMetricViaCandidateTransfer(CandidatePhaseMetric candidatePhaseMetric) {
         try {
-            mChosenProviderFinalPhaseMetric.setSessionId(candidatePhaseMetric.getSessionId());
             mChosenProviderFinalPhaseMetric.setChosenUid(candidatePhaseMetric.getCandidateUid());
 
             mChosenProviderFinalPhaseMetric.setQueryPhaseLatencyMicroseconds(
@@ -284,7 +315,7 @@
      * In the final phase, this helps log use cases that were either pure failures or user
      * canceled. It's expected that {@link #collectFinalPhaseProviderMetricStatus(boolean,
      * ProviderStatusForMetrics) collectFinalPhaseProviderMetricStatus} is called prior to this.
-     * Otherwise, the logging will miss required bits
+     * Otherwise, the logging will miss required bits.
      *
      * @param isUserCanceledError a boolean indicating if the error was due to user cancelling
      */
@@ -318,6 +349,20 @@
     }
 
     /**
+     * Handles aggregate candidate phase metric emits in the RequestSession context, after the
+     * candidate phase completes.
+     *
+     * @param providers a map with known providers and their held metric objects
+     */
+    public void logCandidateAggregateMetrics(Map<String, ProviderSession> providers) {
+        try {
+            mCandidateAggregateMetric.collectAverages(providers);
+        } catch (Exception e) {
+            Slog.i(TAG, "Unexpected error during aggregate candidate logging " + e);
+        }
+    }
+
+    /**
      * Handles the final logging for RequestSession context for the final phase.
      *
      * @param apiStatus the final status of the api being called
@@ -327,9 +372,15 @@
             logApiCalledFinalPhase(mChosenProviderFinalPhaseMetric, mCandidateBrowsingPhaseMetric,
                     apiStatus,
                     ++mSequenceCounter);
+            logApiCalledNoUidFinal(mChosenProviderFinalPhaseMetric, mCandidateBrowsingPhaseMetric,
+                    apiStatus,
+                    ++mSequenceCounter);
         } catch (Exception e) {
             Slog.i(TAG, "Unexpected error during final metric emit: " + e);
         }
     }
 
+    public int getSessionIdTrackTwo() {
+        return mSessionIdTrackTwo;
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index bb3b438..02c6d68 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -447,6 +447,7 @@
 import android.util.DebugUtils;
 import android.util.IndentingPrintWriter;
 import android.util.IntArray;
+import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -1214,7 +1215,7 @@
                 sendDeviceOwnerUserCommand(DeviceAdminReceiver.ACTION_USER_STOPPED, userHandle);
                 if (isManagedProfile(userHandle)) {
                     Slogf.d(LOG_TAG, "Managed profile was stopped");
-                    updatePersonalAppsSuspension(userHandle, false /* unlocked */);
+                    updatePersonalAppsSuspension(userHandle);
                 }
             } else if (Intent.ACTION_USER_SWITCHED.equals(action)) {
                 sendDeviceOwnerUserCommand(DeviceAdminReceiver.ACTION_USER_SWITCHED, userHandle);
@@ -1224,8 +1225,7 @@
                 }
                 if (isManagedProfile(userHandle)) {
                     Slogf.d(LOG_TAG, "Managed profile became unlocked");
-                    final boolean suspended =
-                            updatePersonalAppsSuspension(userHandle, true /* unlocked */);
+                    final boolean suspended = updatePersonalAppsSuspension(userHandle);
                     triggerPolicyComplianceCheckIfNeeded(userHandle, suspended);
                 }
             } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
@@ -1252,13 +1252,13 @@
                 updateSystemUpdateFreezePeriodsRecord(/* saveIfChanged */ true);
                 final int userId = getManagedUserId(getMainUserId());
                 if (userId >= 0) {
-                    updatePersonalAppsSuspension(userId, mUserManager.isUserUnlocked(userId));
+                    updatePersonalAppsSuspension(userId);
                 }
             } else if (ACTION_PROFILE_OFF_DEADLINE.equals(action)) {
                 Slogf.i(LOG_TAG, "Profile off deadline alarm was triggered");
                 final int userId = getManagedUserId(getMainUserId());
                 if (userId >= 0) {
-                    updatePersonalAppsSuspension(userId, mUserManager.isUserUnlocked(userId));
+                    updatePersonalAppsSuspension(userId);
                 } else {
                     Slogf.wtf(LOG_TAG, "Got deadline alarm for nonexistent profile");
                 }
@@ -1268,9 +1268,12 @@
             } else if (ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) {
                 notifyIfManagedSubscriptionsAreUnavailable(
                         UserHandle.of(userHandle), /* managedProfileAvailable= */ false);
+                updatePersonalAppsSuspension(userHandle);
             } else if (ACTION_MANAGED_PROFILE_AVAILABLE.equals(action)) {
                 notifyIfManagedSubscriptionsAreUnavailable(
                         UserHandle.of(userHandle), /* managedProfileAvailable= */ true);
+                final boolean suspended = updatePersonalAppsSuspension(userHandle);
+                triggerPolicyComplianceCheckIfNeeded(userHandle, suspended);
             } else if (LOGIN_ACCOUNTS_CHANGED_ACTION.equals(action)) {
                 calculateHasIncompatibleAccounts();
             }
@@ -3433,7 +3436,7 @@
         final int profileUserHandle = getManagedUserId(userHandle);
         if (profileUserHandle >= 0) {
             // Given that the parent user has just started, profile should be locked.
-            updatePersonalAppsSuspension(profileUserHandle, false /* unlocked */);
+            updatePersonalAppsSuspension(profileUserHandle);
         } else {
             suspendPersonalAppsInternal(userHandle, profileUserHandle, false);
         }
@@ -3611,10 +3614,7 @@
         if (isProfileOwnerOfOrganizationOwnedDevice(userId)
                 && getManagedSubscriptionsPolicy().getPolicyType()
                 == ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS) {
-            String defaultDialerPackageName = getOemDefaultDialerPackage();
-            String defaultSmsPackageName = getOemDefaultSmsPackage();
-            updateDialerAndSmsManagedShortcutsOverrideCache(defaultDialerPackageName,
-                    defaultSmsPackageName);
+            updateDialerAndSmsManagedShortcutsOverrideCache();
         }
 
         startOwnerService(userId, "start-user");
@@ -10728,15 +10728,6 @@
         return UserHandle.USER_NULL;
     }
 
-    private @UserIdInt int getManagedProfileUserId() {
-        for (UserInfo ui : mUserManagerInternal.getUserInfos()) {
-            if (ui.isManagedProfile()) {
-                return ui.id;
-            }
-        }
-        return UserHandle.USER_NULL;
-    }
-
     /**
      * This API is cached: invalidate with invalidateBinderCaches().
      */
@@ -11599,6 +11590,12 @@
         synchronized (getLockObject()) {
             final ActiveAdmin activeAdmin = getParentOfAdminIfRequired(
                     getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), parent);
+
+            if (isManagedProfile(userId)) {
+                mInjector.binderWithCleanCallingIdentity(
+                        () -> updateDialerAndSmsManagedShortcutsOverrideCache());
+            }
+
             if (!Objects.equals(activeAdmin.mSmsPackage, packageName)) {
                 activeAdmin.mSmsPackage = packageName;
                 saveSettingsLocked(caller.getUserId());
@@ -11644,6 +11641,11 @@
         });
         // Only save the package when the setting the role succeeded without exception.
         synchronized (getLockObject()) {
+            if (isManagedProfile(callerUserId)) {
+                mInjector.binderWithCleanCallingIdentity(
+                        () -> updateDialerAndSmsManagedShortcutsOverrideCache());
+            }
+
             final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(callerUserId);
             if (!Objects.equals(admin.mDialerPackage, packageName)) {
                 admin.mDialerPackage = packageName;
@@ -12079,7 +12081,7 @@
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
         Preconditions.checkCallAuthorization(
-                isDeviceOwner(caller) || isProfileOwner(caller));
+                isDefaultDeviceOwner(caller) || isProfileOwner(caller));
 
         if (packageList != null) {
             for (String pkg : packageList) {
@@ -13364,8 +13366,14 @@
                 PolicyDefinition<Boolean> policyDefinition =
                         PolicyDefinition.getPolicyDefinitionForUserRestriction(key);
                 if (enabledFromThisOwner) {
-                    setLocalUserRestrictionInternal(
-                            admin, key, /* enabled= */ true, affectedUserId);
+                    // TODO: Remove this special case - replace with breaking change to require
+                    //  setGlobally to disable ADB
+                    if (key.equals(UserManager.DISALLOW_DEBUGGING_FEATURES) && parent) {
+                        setGlobalUserRestrictionInternal(admin, key, /* enabled= */ true);
+                    } else {
+                        setLocalUserRestrictionInternal(
+                                admin, key, /* enabled= */ true, affectedUserId);
+                    }
                 } else {
                     // Remove any local and global policy that was set by the admin
                     if (!policyDefinition.isLocalOnlyPolicy()) {
@@ -15154,7 +15162,7 @@
                     }
                     DevicePolicyEventLogger
                             .createEvent(DevicePolicyEnums.SET_LOCKTASK_MODE_ENABLED)
-                            .setAdmin(admin.info.getPackageName())
+                            .setAdmin(admin.info == null ? null : admin.info.getPackageName())
                             .setBoolean(isEnabled)
                             .setStrings(pkg)
                             .write();
@@ -20787,7 +20795,7 @@
         }
 
         mInjector.binderWithCleanCallingIdentity(() -> updatePersonalAppsSuspension(
-                callingUserId, mUserManager.isUserUnlocked(callingUserId)));
+                callingUserId));
 
         DevicePolicyEventLogger
                 .createEvent(DevicePolicyEnums.SET_PERSONAL_APPS_SUSPENDED)
@@ -20821,16 +20829,19 @@
     /**
      * Checks whether personal apps should be suspended according to the policy and applies the
      * change if needed.
-     *
-     * @param unlocked whether the profile is currently running unlocked.
      */
-    private boolean updatePersonalAppsSuspension(int profileUserId, boolean unlocked) {
+    private boolean updatePersonalAppsSuspension(int profileUserId) {
         final boolean shouldSuspend;
         synchronized (getLockObject()) {
             final ActiveAdmin profileOwner = getProfileOwnerAdminLocked(profileUserId);
             if (profileOwner != null) {
-                final int notificationState =
-                        updateProfileOffDeadlineLocked(profileUserId, profileOwner, unlocked);
+                // Profile is considered "off" when it is either not running or is running locked
+                // or is in quiet mode, i.e. when the admin cannot sync policies or show UI.
+                boolean profileUserOff =
+                        !mUserManagerInternal.isUserUnlockingOrUnlocked(profileUserId)
+                        || mUserManager.isQuietModeEnabled(UserHandle.of(profileUserId));
+                final int notificationState = updateProfileOffDeadlineLocked(
+                        profileUserId, profileOwner, profileUserOff);
                 final boolean suspendedExplicitly = profileOwner.mSuspendPersonalApps;
                 final boolean suspendedByTimeout = profileOwner.mProfileOffDeadline == -1;
                 Slogf.d(LOG_TAG,
@@ -20855,16 +20866,16 @@
      * @return notification state
      */
     private int updateProfileOffDeadlineLocked(
-            int profileUserId, ActiveAdmin profileOwner, boolean unlocked) {
+            int profileUserId, ActiveAdmin profileOwner, boolean off) {
         final long now = mInjector.systemCurrentTimeMillis();
         if (profileOwner.mProfileOffDeadline != 0 && now > profileOwner.mProfileOffDeadline) {
-            Slogf.i(LOG_TAG, "Profile off deadline has been reached, unlocked: " + unlocked);
+            Slogf.i(LOG_TAG, "Profile off deadline has been reached, off: " + off);
             if (profileOwner.mProfileOffDeadline != -1) {
                 // Move the deadline far to the past so that it cannot be rolled back by TZ change.
                 profileOwner.mProfileOffDeadline = -1;
                 saveSettingsLocked(profileUserId);
             }
-            return unlocked ? PROFILE_OFF_NOTIFICATION_NONE : PROFILE_OFF_NOTIFICATION_SUSPENDED;
+            return off ? PROFILE_OFF_NOTIFICATION_SUSPENDED : PROFILE_OFF_NOTIFICATION_NONE;
         }
         boolean shouldSaveSettings = false;
         if (profileOwner.mSuspendPersonalApps) {
@@ -20881,7 +20892,7 @@
             profileOwner.mProfileOffDeadline = 0;
             shouldSaveSettings = true;
         } else if (profileOwner.mProfileOffDeadline == 0
-                && (profileOwner.mProfileMaximumTimeOffMillis != 0 && !unlocked)) {
+                && (profileOwner.mProfileMaximumTimeOffMillis != 0 && off)) {
             // There profile is locked and there is a policy, but the deadline is not set -> set the
             // deadline.
             Slogf.i(LOG_TAG, "Profile off deadline is set.");
@@ -20895,7 +20906,7 @@
 
         final long alarmTime;
         final int notificationState;
-        if (unlocked || profileOwner.mProfileOffDeadline == 0) {
+        if (!off || profileOwner.mProfileOffDeadline == 0) {
             alarmTime = 0;
             notificationState = PROFILE_OFF_NOTIFICATION_NONE;
         } else if (profileOwner.mProfileOffDeadline - now < MANAGED_PROFILE_OFF_WARNING_PERIOD) {
@@ -21169,7 +21180,7 @@
         }
 
         mInjector.binderWithCleanCallingIdentity(
-                () -> updatePersonalAppsSuspension(userId, mUserManager.isUserUnlocked()));
+                () -> updatePersonalAppsSuspension(userId));
 
         DevicePolicyEventLogger
                 .createEvent(DevicePolicyEnums.SET_MANAGED_PROFILE_MAXIMUM_TIME_OFF)
@@ -21814,10 +21825,9 @@
         final AccountManager accountManager = mContext.createContextAsUser(
                         UserHandle.of(sourceUserId), /* flags= */ 0)
                 .getSystemService(AccountManager.class);
-        final AccountManagerFuture<Bundle> bundle = accountManager.removeAccount(account,
-                null, null /* callback */, null /* handler */);
         try {
-            final Bundle result = bundle.getResult();
+            final Bundle result = accountManager.removeAccount(account,
+                    null, null /* callback */, null /* handler */).getResult(60, TimeUnit.SECONDS);
             if (result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, /* default */ false)) {
                 Slogf.i(LOG_TAG, "Account removed from the primary user.");
             } else {
@@ -22718,7 +22728,7 @@
         }
 
         private void handleFinancedDeviceKioskRoleChange() {
-            if (!isPermissionCheckFlagEnabled()) {
+            if (!isPolicyEngineForFinanceFlagEnabled()) {
                 return;
             }
             Slog.i(LOG_TAG, "Handling action " + ACTION_DEVICE_FINANCING_STATE_CHANGED);
@@ -23934,8 +23944,7 @@
                 Slogf.w(LOG_TAG, "Couldn't install sms app, sms app package is null");
             }
 
-            updateDialerAndSmsManagedShortcutsOverrideCache(defaultDialerPackageName,
-                    defaultSmsPackageName);
+            updateDialerAndSmsManagedShortcutsOverrideCache();
         } catch (RemoteException re) {
             // shouldn't happen
             Slogf.wtf(LOG_TAG, "Failed to install dialer/sms app", re);
@@ -23951,17 +23960,28 @@
         return mContext.getString(R.string.config_defaultSms);
     }
 
-    private void updateDialerAndSmsManagedShortcutsOverrideCache(
-            String defaultDialerPackageName, String defaultSmsPackageName) {
+    private void updateDialerAndSmsManagedShortcutsOverrideCache() {
         ArrayMap<String, String> shortcutOverrides = new ArrayMap<>();
+        int managedUserId = getManagedUserId();
+        List<String> dialerRoleHolders = mRoleManager.getRoleHoldersAsUser(RoleManager.ROLE_DIALER,
+                UserHandle.of(managedUserId));
+        List<String> smsRoleHolders = mRoleManager.getRoleHoldersAsUser(RoleManager.ROLE_SMS,
+                UserHandle.of(managedUserId));
 
-        if (defaultDialerPackageName != null) {
-            shortcutOverrides.put(defaultDialerPackageName, defaultDialerPackageName);
+        String dialerPackageToOverride = getOemDefaultDialerPackage();
+        String smsPackageToOverride = getOemDefaultSmsPackage();
+
+        // To get the default app, we can get all the role holders and get the first element.
+        if (dialerPackageToOverride != null) {
+            shortcutOverrides.put(dialerPackageToOverride,
+                    dialerRoleHolders.isEmpty() ? dialerPackageToOverride
+                            : dialerRoleHolders.get(0));
+        }
+        if (smsPackageToOverride != null) {
+            shortcutOverrides.put(smsPackageToOverride,
+                    smsRoleHolders.isEmpty() ? smsPackageToOverride : smsRoleHolders.get(0));
         }
 
-        if (defaultSmsPackageName != null) {
-            shortcutOverrides.put(defaultSmsPackageName, defaultSmsPackageName);
-        }
         mPolicyCache.setLauncherShortcutOverrides(shortcutOverrides);
     }
 
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 acfea85..5d3b913 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -769,7 +769,7 @@
 
         BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
                 PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
-        queue.setPrioritizeEarliest(true);
+        queue.addPrioritizeEarliestRequest();
         long timeCounter = 100;
 
         enqueueOrReplaceBroadcast(queue,
@@ -814,6 +814,28 @@
         queue.makeActiveNextPending();
         assertEquals(AppWidgetManager.ACTION_APPWIDGET_UPDATE,
                 queue.getActive().intent.getAction());
+
+
+        queue.removePrioritizeEarliestRequest();
+
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_BOOT_COMPLETED)
+                        .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)),
+                0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED)
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, timeCounter++);
+
+        // Once the request to prioritize earliest is removed, we should expect broadcasts
+        // to be dispatched in the order of foreground, normal and then offload.
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_LOCALE_CHANGED, queue.getActive().intent.getAction());
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_TIMEZONE_CHANGED, queue.getActive().intent.getAction());
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_BOOT_COMPLETED, queue.getActive().intent.getAction());
     }
 
     /**
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 7be1d7c..3a8d2c9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -1890,6 +1890,36 @@
         assertTrue(mQueue.isBeyondBarrierLocked(afterSecond));
     }
 
+    @Test
+    public void testWaitForBroadcastDispatch() throws Exception {
+        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+        final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN);
+
+        final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
+        assertTrue(mQueue.isDispatchedLocked(timeTick));
+
+        final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+        enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
+                List.of(makeRegisteredReceiver(receiverApp))));
+
+        assertTrue(mQueue.isDispatchedLocked(timeTick));
+        assertFalse(mQueue.isDispatchedLocked(timezone));
+
+        enqueueBroadcast(makeBroadcastRecord(timeTick, callerApp,
+                List.of(makeRegisteredReceiver(receiverApp))));
+
+        assertFalse(mQueue.isDispatchedLocked(timeTick));
+        assertFalse(mQueue.isDispatchedLocked(timezone));
+
+        mLooper.release();
+
+        mQueue.waitForDispatched(timeTick, LOG_WRITER_INFO);
+        assertTrue(mQueue.isDispatchedLocked(timeTick));
+
+        mQueue.waitForDispatched(timezone, LOG_WRITER_INFO);
+        assertTrue(mQueue.isDispatchedLocked(timezone));
+    }
+
     /**
      * Verify that we OOM adjust for manifest receivers.
      */
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index f8955ed..3d0163d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -172,12 +172,12 @@
         sImageWallpaperComponentName = ComponentName.unflattenFromString(
                 sContext.getResources().getString(R.string.image_wallpaper_component));
         // Mock default wallpaper as image wallpaper if there is no pre-defined default wallpaper.
-        sDefaultWallpaperComponent = WallpaperManager.getDefaultWallpaperComponent(sContext);
+        sDefaultWallpaperComponent = WallpaperManager.getCmfDefaultWallpaperComponent(sContext);
 
         if (sDefaultWallpaperComponent == null) {
             sDefaultWallpaperComponent = sImageWallpaperComponentName;
             doReturn(sImageWallpaperComponentName).when(() ->
-                    WallpaperManager.getDefaultWallpaperComponent(any()));
+                    WallpaperManager.getCmfDefaultWallpaperComponent(any()));
         } else {
             sContext.addMockService(sDefaultWallpaperComponent, sWallpaperService);
         }
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index 11e4120..5f2db79 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -208,8 +208,8 @@
         mMockConnection = new MockWindowMagnificationConnection(true);
         mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
 
-        mMagnificationController = new MagnificationController(mService, globalLock, mContext,
-                mScreenMagnificationController, mWindowMagnificationManager, mScaleProvider);
+        mMagnificationController = spy(new MagnificationController(mService, globalLock, mContext,
+                mScreenMagnificationController, mWindowMagnificationManager, mScaleProvider));
         mMagnificationController.setMagnificationCapabilities(
                 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL);
 
@@ -774,15 +774,21 @@
         verify(mWindowMagnificationManager, times(2)).removeMagnificationButton(eq(TEST_DISPLAY));
     }
 
+    @Test public void activateWindowMagnification_triggerCallback() throws RemoteException {
+        setMagnificationEnabled(MODE_WINDOW);
+        verify(mMagnificationController).onWindowMagnificationActivationState(
+                eq(TEST_DISPLAY), eq(true));
+    }
     @Test
-    public void onWindowMagnificationActivationState_windowActivated_logWindowDuration() {
-        MagnificationController spyController = spy(mMagnificationController);
-        spyController.onWindowMagnificationActivationState(TEST_DISPLAY, true);
+    public void deactivateWindowMagnification_windowActivated_triggerCallbackAndLogUsage()
+            throws RemoteException {
+        setMagnificationEnabled(MODE_WINDOW);
+        mWindowMagnificationManager.disableWindowMagnification(TEST_DISPLAY, /* clear= */ true);
 
-        spyController.onWindowMagnificationActivationState(TEST_DISPLAY, false);
-
-        verify(spyController).logMagnificationUsageState(
-                eq(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW), anyLong());
+        verify(mMagnificationController).onWindowMagnificationActivationState(
+                eq(TEST_DISPLAY), eq(false));
+        verify(mMagnificationController).logMagnificationUsageState(
+                eq(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW), anyLong(), eq(DEFAULT_SCALE));
     }
 
     @Test
@@ -906,15 +912,22 @@
         assertEquals(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN, lastActivatedMode);
     }
 
+    @Test public void activateFullScreenMagnification_triggerCallback() throws RemoteException {
+        setMagnificationEnabled(MODE_FULLSCREEN);
+        verify(mMagnificationController).onFullScreenMagnificationActivationState(
+                eq(TEST_DISPLAY), eq(true));
+    }
+
     @Test
-    public void onFullScreenMagnificationActivationState_fullScreenEnabled_logFullScreenDuration() {
-        MagnificationController spyController = spy(mMagnificationController);
-        spyController.onFullScreenMagnificationActivationState(TEST_DISPLAY, true);
+    public void deactivateFullScreenMagnification_fullScreenEnabled_triggerCallbackAndLogUsage()
+            throws RemoteException {
+        setMagnificationEnabled(MODE_FULLSCREEN);
+        mScreenMagnificationController.reset(TEST_DISPLAY, /* animate= */ false);
 
-        spyController.onFullScreenMagnificationActivationState(TEST_DISPLAY, false);
-
-        verify(spyController).logMagnificationUsageState(
-                eq(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN), anyLong());
+        verify(mMagnificationController).onFullScreenMagnificationActivationState(
+                eq(TEST_DISPLAY), eq(false));
+        verify(mMagnificationController).logMagnificationUsageState(
+                eq(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN), anyLong(), eq(DEFAULT_SCALE));
     }
 
     @Test
@@ -1106,48 +1119,44 @@
 
     @Test
     public void imeWindowStateShown_windowMagnifying_logWindowMode() {
-        MagnificationController spyController = spy(mMagnificationController);
-        spyController.onWindowMagnificationActivationState(TEST_DISPLAY, true);
+        mMagnificationController.onWindowMagnificationActivationState(TEST_DISPLAY, true);
 
-        spyController.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
+        mMagnificationController.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
 
-        verify(spyController).logMagnificationModeWithIme(
+        verify(mMagnificationController).logMagnificationModeWithIme(
                 eq(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW));
     }
 
     @Test
     public void imeWindowStateShown_fullScreenMagnifying_logFullScreenMode() {
-        MagnificationController spyController = spy(mMagnificationController);
-        spyController.onFullScreenMagnificationActivationState(TEST_DISPLAY, true);
+        mMagnificationController.onFullScreenMagnificationActivationState(TEST_DISPLAY, true);
 
-        spyController.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
+        mMagnificationController.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
 
-        verify(spyController).logMagnificationModeWithIme(
+        verify(mMagnificationController).logMagnificationModeWithIme(
                 eq(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN));
     }
 
     @Test
     public void imeWindowStateShown_noMagnifying_noLogAnyMode() {
-        MagnificationController spyController = spy(mMagnificationController);
-        spyController.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
+        mMagnificationController.onImeWindowVisibilityChanged(TEST_DISPLAY, true);
 
-        verify(spyController, never()).logMagnificationModeWithIme(anyInt());
+        verify(mMagnificationController, never()).logMagnificationModeWithIme(anyInt());
     }
 
     @Test
     public void imeWindowStateHidden_windowMagnifying_noLogAnyMode() {
-        MagnificationController spyController = spy(mMagnificationController);
-        spyController.onFullScreenMagnificationActivationState(TEST_DISPLAY, true);
+        mMagnificationController.onFullScreenMagnificationActivationState(
+                TEST_DISPLAY, true);
 
-        verify(spyController, never()).logMagnificationModeWithIme(anyInt());
+        verify(mMagnificationController, never()).logMagnificationModeWithIme(anyInt());
     }
 
     @Test
     public void imeWindowStateHidden_fullScreenMagnifying_noLogAnyMode() {
-        MagnificationController spyController = spy(mMagnificationController);
-        spyController.onWindowMagnificationActivationState(TEST_DISPLAY, true);
+        mMagnificationController.onWindowMagnificationActivationState(TEST_DISPLAY, true);
 
-        verify(spyController, never()).logMagnificationModeWithIme(anyInt());
+        verify(mMagnificationController, never()).logMagnificationModeWithIme(anyInt());
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/am/UidObserverControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UidObserverControllerTest.java
index 46974cf7..37afc7f 100644
--- a/services/tests/servicestests/src/com/android/server/am/UidObserverControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UidObserverControllerTest.java
@@ -217,7 +217,8 @@
     private void registerObserver(IUidObserver observer, int which, int cutpoint,
             String callingPackage, int callingUid) {
         when(observer.asBinder()).thenReturn((IBinder) observer);
-        mUidObserverController.register(observer, which, cutpoint, callingPackage, callingUid);
+        mUidObserverController.register(observer, which, cutpoint, callingPackage, callingUid,
+                /*uids*/null);
         Mockito.reset(observer);
     }
 
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 2ea56f6..39de2cf 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -8600,6 +8600,8 @@
 
     private void setUserUnlocked(int userHandle, boolean unlocked) {
         when(getServices().userManager.isUserUnlocked(eq(userHandle))).thenReturn(unlocked);
+        when(getServices().userManagerInternal.isUserUnlockingOrUnlocked(eq(userHandle)))
+                .thenReturn(unlocked);
     }
 
     private void prepareMocksForSetMaximumProfileTimeOff() throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelperTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelperTest.java
index 2a9c18c..bbd9223 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelperTest.java
@@ -104,7 +104,7 @@
     @Before
     public void setUp() throws Exception {
         Context context = InstrumentationRegistry.getTargetContext();
-        mDatabaseHelper = new RecoverableKeyStoreDbHelper(context, 7);
+        mDatabaseHelper = new RecoverableKeyStoreDbHelper(context);
         mDatabase = SQLiteDatabase.create(null);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
index e223a97..8bc14fc 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
@@ -67,7 +67,7 @@
     public void setUp() {
         Context context = InstrumentationRegistry.getTargetContext();
         mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME);
-        mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context, 7);
+        mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context);
     }
 
     @After
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index ff6c534..7d028d2 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -5555,6 +5555,8 @@
     public void testVisitUris() throws Exception {
         final Uri audioContents = Uri.parse("content://com.example/audio");
         final Uri backgroundImage = Uri.parse("content://com.example/background");
+        final Icon smallIcon = Icon.createWithContentUri("content://media/small/icon");
+        final Icon largeIcon = Icon.createWithContentUri("content://media/large/icon");
         final Icon personIcon1 = Icon.createWithContentUri("content://media/person1");
         final Icon personIcon2 = Icon.createWithContentUri("content://media/person2");
         final Icon personIcon3 = Icon.createWithContentUri("content://media/person3");
@@ -5588,7 +5590,8 @@
 
         Notification n = new Notification.Builder(mContext, "a")
                 .setContentTitle("notification with uris")
-                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setSmallIcon(smallIcon)
+                .setLargeIcon(largeIcon)
                 .addExtras(extras)
                 .build();
 
@@ -5596,6 +5599,8 @@
         n.visitUris(visitor);
         verify(visitor, times(1)).accept(eq(audioContents));
         verify(visitor, times(1)).accept(eq(backgroundImage));
+        verify(visitor, times(1)).accept(eq(smallIcon.getUri()));
+        verify(visitor, times(1)).accept(eq(largeIcon.getUri()));
         verify(visitor, times(1)).accept(eq(personIcon1.getUri()));
         verify(visitor, times(1)).accept(eq(personIcon2.getUri()));
         verify(visitor, times(1)).accept(eq(personIcon3.getUri()));
@@ -5604,6 +5609,68 @@
     }
 
     @Test
+    public void testVisitUris_audioContentsString() throws Exception {
+        final Uri audioContents = Uri.parse("content://com.example/audio");
+
+        Bundle extras = new Bundle();
+        extras.putString(Notification.EXTRA_AUDIO_CONTENTS_URI, audioContents.toString());
+
+        Notification n = new Notification.Builder(mContext, "a")
+                .setContentTitle("notification with uris")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .addExtras(extras)
+                .build();
+
+        Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class);
+        n.visitUris(visitor);
+        verify(visitor, times(1)).accept(eq(audioContents));
+    }
+
+    @Test
+    public void testVisitUris_messagingStyle() {
+        final Icon personIcon1 = Icon.createWithContentUri("content://media/person1");
+        final Icon personIcon2 = Icon.createWithContentUri("content://media/person2");
+        final Icon personIcon3 = Icon.createWithContentUri("content://media/person3");
+        final Person person1 = new Person.Builder()
+                .setName("Messaging Person 1")
+                .setIcon(personIcon1)
+                .build();
+        final Person person2 = new Person.Builder()
+                .setName("Messaging Person 2")
+                .setIcon(personIcon2)
+                .build();
+        final Person person3 = new Person.Builder()
+                .setName("Messaging Person 3")
+                .setIcon(personIcon3)
+                .build();
+        Icon shortcutIcon = Icon.createWithContentUri("content://media/shortcut");
+
+        Notification.Builder builder = new Notification.Builder(mContext, "a")
+                .setCategory(Notification.CATEGORY_MESSAGE)
+                .setContentTitle("new message!")
+                .setContentText("Conversation Notification")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon);
+        Notification.MessagingStyle.Message message1 = new Notification.MessagingStyle.Message(
+                "Marco?", System.currentTimeMillis(), person2);
+        Notification.MessagingStyle.Message message2 = new Notification.MessagingStyle.Message(
+                "Polo!", System.currentTimeMillis(), person3);
+        Notification.MessagingStyle style = new Notification.MessagingStyle(person1)
+                .addMessage(message1)
+                .addMessage(message2)
+                .setShortcutIcon(shortcutIcon);
+        builder.setStyle(style);
+        Notification n = builder.build();
+
+        Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class);
+        n.visitUris(visitor);
+
+        verify(visitor, times(1)).accept(eq(shortcutIcon.getUri()));
+        verify(visitor, times(1)).accept(eq(personIcon1.getUri()));
+        verify(visitor, times(1)).accept(eq(personIcon2.getUri()));
+        verify(visitor, times(1)).accept(eq(personIcon3.getUri()));
+    }
+
+    @Test
     public void testVisitUris_callStyle() {
         Icon personIcon = Icon.createWithContentUri("content://media/person");
         Icon verificationIcon = Icon.createWithContentUri("content://media/verification");
@@ -5627,24 +5694,6 @@
     }
 
     @Test
-    public void testVisitUris_audioContentsString() throws Exception {
-        final Uri audioContents = Uri.parse("content://com.example/audio");
-
-        Bundle extras = new Bundle();
-        extras.putString(Notification.EXTRA_AUDIO_CONTENTS_URI, audioContents.toString());
-
-        Notification n = new Notification.Builder(mContext, "a")
-                .setContentTitle("notification with uris")
-                .setSmallIcon(android.R.drawable.sym_def_app_icon)
-                .addExtras(extras)
-                .build();
-
-        Consumer<Uri> visitor = (Consumer<Uri>) spy(Consumer.class);
-        n.visitUris(visitor);
-        verify(visitor, times(1)).accept(eq(audioContents));
-    }
-
-    @Test
     public void testSetNotificationPolicy_preP_setOldFields() {
         ZenModeHelper mZenModeHelper = mock(ZenModeHelper.class);
         mService.mZenModeHelper = mZenModeHelper;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
new file mode 100644
index 0000000..27677e1
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
@@ -0,0 +1,778 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.Person;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Icon;
+import android.media.session.MediaSession;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.util.Log;
+import android.view.View;
+import android.widget.RemoteViews;
+
+import androidx.annotation.NonNull;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.UiServiceTestCase;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.truth.Expect;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+import java.io.PrintWriter;
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.annotation.Nullable;
+
+@RunWith(AndroidJUnit4.class)
+public class NotificationVisitUrisTest extends UiServiceTestCase {
+
+    private static final String TAG = "VisitUrisTest";
+
+    // Methods that are known to add Uris that are *NOT* verified.
+    // This list should be emptied! Items can be removed as bugs are fixed.
+    private static final Multimap<Class<?>, String> KNOWN_BAD =
+            ImmutableMultimap.<Class<?>, String>builder()
+                    .put(Notification.Builder.class, "setPublicVersion") // b/276294099
+                    .putAll(RemoteViews.class, "addView", "addStableView") // b/277740082
+                    .put(RemoteViews.class, "setIcon") // b/281018094
+                    .put(Notification.WearableExtender.class, "addAction") // TODO: b/281044385
+                    .put(Person.Builder.class, "setUri") // TODO: b/281044385
+                    .put(RemoteViews.class, "setRemoteAdapter") // TODO: b/281044385
+                    .build();
+
+    // Types that we can't really produce. No methods receiving these parameters will be invoked.
+    private static final ImmutableSet<Class<?>> UNUSABLE_TYPES =
+            ImmutableSet.of(Consumer.class, IBinder.class, MediaSession.Token.class, Parcel.class,
+                    PrintWriter.class, Resources.Theme.class, View.class);
+
+    // Maximum number of times we allow generating the same class recursively.
+    // E.g. new RemoteViews.addView(new RemoteViews()) but stop there.
+    private static final int MAX_RECURSION = 2;
+
+    // Number of times a method called addX(X) will be called.
+    private static final int NUM_ADD_CALLS = 2;
+
+    // Number of elements to put in a generated array, e.g. for calling setGloops(Gloop[] gloops).
+    private static final int NUM_ELEMENTS_IN_ARRAY = 3;
+
+    // Constructors that should be used to create instances of specific classes. Overrides scoring.
+    private static final ImmutableMap<Class<?>, Constructor<?>> PREFERRED_CONSTRUCTORS;
+
+    static {
+        try {
+            PREFERRED_CONSTRUCTORS = ImmutableMap.of(
+                    Notification.Builder.class,
+                    Notification.Builder.class.getConstructor(Context.class, String.class));
+        } catch (NoSuchMethodException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static final Multimap<Class<?>, String> EXCLUDED_SETTERS =
+            ImmutableMultimap.<Class<?>, String>builder()
+                    // Handled by testAllStyles().
+                    .put(Notification.Builder.class, "setStyle")
+                    // Handled by testAllExtenders().
+                    .put(Notification.Builder.class, "extend")
+                    // Handled by testAllActionExtenders().
+                    .put(Notification.Action.Builder.class, "extend")
+                    // Overwrites icon supplied to constructor.
+                    .put(Notification.BubbleMetadata.Builder.class, "setIcon")
+                    // Discards previously-added actions.
+                    .put(RemoteViews.class, "mergeRemoteViews")
+                    .build();
+
+    private Context mContext;
+
+    @Rule
+    public final Expect expect = Expect.create();
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+    }
+
+    @Test // This is a meta-test, checks that the generators are not broken.
+    public void verifyTest() {
+        Generated<Notification> notification = buildNotification(mContext, /* styleClass= */ null,
+                /* extenderClass= */ null, /* actionExtenderClass= */ null,
+                /* includeRemoteViews= */ true);
+        assertThat(notification.includedUris.size()).isAtLeast(20);
+    }
+
+    @Test
+    public void testPlainNotification() throws Exception {
+        Generated<Notification> notification = buildNotification(mContext, /* styleClass= */ null,
+                /* extenderClass= */ null, /* actionExtenderClass= */ null,
+                /* includeRemoteViews= */ false);
+        verifyAllUrisAreVisited(notification.value, notification.includedUris,
+                "Plain Notification");
+    }
+
+    @Test
+    public void testRemoteViews() throws Exception {
+        Generated<Notification> notification = buildNotification(mContext, /* styleClass= */ null,
+                /* extenderClass= */ null, /* actionExtenderClass= */ null,
+                /* includeRemoteViews= */ true);
+        verifyAllUrisAreVisited(notification.value, notification.includedUris,
+                "Notification with Remote Views");
+    }
+
+    @Test
+    public void testAllStyles() throws Exception {
+        for (Class<?> styleClass : ReflectionUtils.getConcreteSubclasses(Notification.Style.class,
+                Notification.class)) {
+            Generated<Notification> notification = buildNotification(mContext, styleClass,
+                    /* extenderClass= */ null, /* actionExtenderClass= */ null,
+                    /* includeRemoteViews= */ false);
+            verifyAllUrisAreVisited(notification.value, notification.includedUris,
+                    String.format("Style (%s)", styleClass.getSimpleName()));
+        }
+    }
+
+    @Test
+    public void testAllExtenders() throws Exception {
+        for (Class<?> extenderClass : ReflectionUtils.getConcreteSubclasses(
+                Notification.Extender.class, Notification.class)) {
+            Generated<Notification> notification = buildNotification(mContext,
+                    /* styleClass= */ null, extenderClass, /* actionExtenderClass= */ null,
+                    /* includeRemoteViews= */ false);
+            verifyAllUrisAreVisited(notification.value, notification.includedUris,
+                    String.format("Extender (%s)", extenderClass.getSimpleName()));
+        }
+    }
+
+    @Test
+    public void testAllActionExtenders() throws Exception {
+        for (Class<?> actionExtenderClass : ReflectionUtils.getConcreteSubclasses(
+                Notification.Action.Extender.class, Notification.Action.class)) {
+            Generated<Notification> notification = buildNotification(mContext,
+                    /* styleClass= */ null, /* extenderClass= */ null, actionExtenderClass,
+                    /* includeRemoteViews= */ false);
+            verifyAllUrisAreVisited(notification.value, notification.includedUris,
+                    String.format("Action.Extender (%s)", actionExtenderClass.getSimpleName()));
+        }
+    }
+
+    private void verifyAllUrisAreVisited(Notification notification, List<Uri> includedUris,
+            String notificationTypeMessage) throws Exception {
+        Consumer<Uri> visitor = (Consumer<Uri>) Mockito.mock(Consumer.class);
+        ArgumentCaptor<Uri> visitedUriCaptor = ArgumentCaptor.forClass(Uri.class);
+
+        notification.visitUris(visitor);
+
+        Mockito.verify(visitor, Mockito.atLeastOnce()).accept(visitedUriCaptor.capture());
+        List<Uri> visitedUris = new ArrayList<>(visitedUriCaptor.getAllValues());
+        visitedUris.remove(null);
+
+        expect.withMessage(notificationTypeMessage)
+                .that(visitedUris)
+                .containsAtLeastElementsIn(includedUris);
+        expect.that(KNOWN_BAD).isNotEmpty(); // Once empty, switch to containsExactlyElementsIn()
+    }
+
+    private static Generated<Notification> buildNotification(Context context,
+            @Nullable Class<?> styleClass, @Nullable Class<?> extenderClass,
+            @Nullable Class<?> actionExtenderClass, boolean includeRemoteViews) {
+        SpecialParameterGenerator specialGenerator = new SpecialParameterGenerator(context);
+        Set<Class<?>> excludedClasses = includeRemoteViews
+                ? ImmutableSet.of()
+                : ImmutableSet.of(RemoteViews.class);
+        Location location = Location.root(Notification.Builder.class);
+
+        Notification.Builder builder = new Notification.Builder(context, "channelId");
+        invokeAllSetters(builder, location, /* allOverloads= */ false,
+                /* includingVoidMethods= */ false, excludedClasses, specialGenerator);
+
+        if (styleClass != null) {
+            builder.setStyle((Notification.Style) generateObject(styleClass,
+                    location.plus("setStyle", Notification.Style.class),
+                    excludedClasses, specialGenerator));
+        }
+        if (extenderClass != null) {
+            builder.extend((Notification.Extender) generateObject(extenderClass,
+                    location.plus("extend", Notification.Extender.class),
+                    excludedClasses, specialGenerator));
+        }
+        if (actionExtenderClass != null) {
+            Location actionLocation = location.plus("addAction", Notification.Action.class);
+            Notification.Action.Builder actionBuilder =
+                    (Notification.Action.Builder) generateObject(
+                            Notification.Action.Builder.class, actionLocation, excludedClasses,
+                            specialGenerator);
+            actionBuilder.extend((Notification.Action.Extender) generateObject(actionExtenderClass,
+                    actionLocation.plus(
+                            Notification.Action.Builder.class).plus("extend",
+                            Notification.Action.Extender.class),
+                    excludedClasses, specialGenerator));
+            builder.addAction(actionBuilder.build());
+        }
+
+        return new Generated<>(builder.build(), specialGenerator.getGeneratedUris());
+    }
+
+    private static Object generateObject(Class<?> clazz, Location where,
+            Set<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) {
+        if (excludingClasses.contains(clazz)) {
+            throw new IllegalArgumentException(
+                    String.format("Asked to generate a %s but it's part of the excluded set (%s)",
+                            clazz, excludingClasses));
+        }
+
+        if (SpecialParameterGenerator.canGenerate(clazz)) {
+            return specialGenerator.generate(clazz, where);
+        }
+        if (clazz.isEnum()) {
+            return clazz.getEnumConstants()[0];
+        }
+        if (clazz.isArray()) {
+            Object arrayValue = Array.newInstance(clazz.getComponentType(), NUM_ELEMENTS_IN_ARRAY);
+            for (int i = 0; i < Array.getLength(arrayValue); i++) {
+                Array.set(arrayValue, i,
+                        generateObject(clazz.getComponentType(), where, excludingClasses,
+                                specialGenerator));
+            }
+            return arrayValue;
+        }
+
+        Log.i(TAG, "About to generate a(n)" + clazz.getName());
+
+        // Need to construct one of these. Look for a Builder inner class... and also look for a
+        // Builder as a "sibling" class; CarExtender.UnreadConversation does this :(
+        Stream<Class<?>> maybeBuilders =
+                Stream.concat(Arrays.stream(clazz.getDeclaredClasses()),
+                        clazz.getDeclaringClass() != null
+                                ? Arrays.stream(clazz.getDeclaringClass().getDeclaredClasses())
+                                : Stream.empty());
+        Optional<Class<?>> clazzBuilder =
+                maybeBuilders
+                        .filter(maybeBuilder -> maybeBuilder.getSimpleName().equals("Builder"))
+                        .filter(maybeBuilder ->
+                                Arrays.stream(maybeBuilder.getMethods()).anyMatch(
+                                        m -> m.getName().equals("build")
+                                                && m.getParameterCount() == 0
+                                                && m.getReturnType().equals(clazz)))
+                        .findFirst();
+
+
+        if (clazzBuilder.isPresent()) {
+            try {
+                // Found a Builder! Create an instance of it, call its setters, and call build()
+                // on it.
+                Object builder = constructEmpty(clazzBuilder.get(), where.plus(clazz),
+                        excludingClasses, specialGenerator);
+                invokeAllSetters(builder, where.plus(clazz).plus(clazzBuilder.get()),
+                        /* allOverloads= */ false, /* includingVoidMethods= */ false,
+                        excludingClasses, specialGenerator);
+
+                Method buildMethod = builder.getClass().getMethod("build");
+                Object built = buildMethod.invoke(builder);
+                assertThat(built).isInstanceOf(clazz);
+                return built;
+            } catch (Exception e) {
+                throw new UnsupportedOperationException(
+                        "Error using Builder " + clazzBuilder.get().getName(), e);
+            }
+        }
+
+        // If no X.Builder, look for X() constructor.
+        try {
+            Object instance = constructEmpty(clazz, where, excludingClasses, specialGenerator);
+            invokeAllSetters(instance, where.plus(clazz), /* allOverloads= */ false,
+                    /* includingVoidMethods= */ false, excludingClasses, specialGenerator);
+            return instance;
+        } catch (Exception e) {
+            throw new UnsupportedOperationException("Error generating a(n) " + clazz.getName(), e);
+        }
+    }
+
+    private static Object constructEmpty(Class<?> clazz, Location where,
+            Set<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) {
+        Constructor<?> bestConstructor;
+        if (PREFERRED_CONSTRUCTORS.containsKey(clazz)) {
+            // Use the preferred constructor.
+            bestConstructor = PREFERRED_CONSTRUCTORS.get(clazz);
+        } else if (Notification.Extender.class.isAssignableFrom(clazz)
+                || Notification.Action.Extender.class.isAssignableFrom(clazz)) {
+            // For extenders, prefer the empty constructors. The others are "partial-copy"
+            // constructors and do not read all fields from the supplied Notification/Action.
+            try {
+                bestConstructor = clazz.getConstructor();
+            } catch (Exception e) {
+                throw new UnsupportedOperationException(
+                        String.format("Extender class %s doesn't have a zero-parameter constructor",
+                                clazz.getName()));
+            }
+        } else {
+            // Look for a non-deprecated constructor using any of the "interesting" parameters.
+            List<Constructor<?>> allConstructors = Arrays.stream(clazz.getConstructors())
+                    .filter(c -> c.getAnnotation(Deprecated.class) == null)
+                    .collect(Collectors.toList());
+            bestConstructor = ReflectionUtils.chooseBestOverload(allConstructors, where);
+        }
+        if (bestConstructor != null) {
+            try {
+                Object[] constructorParameters = generateParameters(bestConstructor,
+                        where.plus(clazz), excludingClasses, specialGenerator);
+                Log.i(TAG, "Invoking " + ReflectionUtils.methodToString(bestConstructor) + " with "
+                        + Arrays.toString(constructorParameters));
+                return bestConstructor.newInstance(constructorParameters);
+            } catch (Exception e) {
+                throw new UnsupportedOperationException(
+                        String.format("Error invoking constructor %s",
+                                ReflectionUtils.methodToString(bestConstructor)), e);
+            }
+        }
+
+        // Look for a "static constructor", i.e. some factory method on the same class.
+        List<Method> factoryMethods = Arrays.stream(clazz.getMethods())
+                .filter(m -> Modifier.isStatic(m.getModifiers()) && clazz.equals(m.getReturnType()))
+                .collect(Collectors.toList());
+        Method bestFactoryMethod = ReflectionUtils.chooseBestOverload(factoryMethods, where);
+        if (bestFactoryMethod != null) {
+            try {
+                Object[] methodParameters = generateParameters(bestFactoryMethod, where.plus(clazz),
+                        excludingClasses, specialGenerator);
+                Log.i(TAG,
+                        "Invoking " + ReflectionUtils.methodToString(bestFactoryMethod) + " with "
+                                + Arrays.toString(methodParameters));
+                return bestFactoryMethod.invoke(null, methodParameters);
+            } catch (Exception e) {
+                throw new UnsupportedOperationException(
+                        "Error invoking constructor-like static method "
+                                + bestFactoryMethod.getName() + " for " + clazz.getName(), e);
+            }
+        }
+
+        throw new UnsupportedOperationException(
+                "Couldn't find a way to construct a(n) " + clazz.getName());
+    }
+
+    private static void invokeAllSetters(Object instance, Location where, boolean allOverloads,
+            boolean includingVoidMethods, Set<Class<?>> excludingParameterTypes,
+            SpecialParameterGenerator specialGenerator) {
+        for (Method setter : ReflectionUtils.getAllSetters(instance.getClass(), where,
+                allOverloads, includingVoidMethods, excludingParameterTypes)) {
+            try {
+                int numInvocations = setter.getName().startsWith("add") ? NUM_ADD_CALLS : 1;
+                for (int i = 0; i < numInvocations; i++) {
+
+                    // If the method is a "known bad" (i.e. adds Uris that aren't visited later)
+                    // then still call it, but don't add to list of generated Uris. Easiest way is
+                    // to use a throw-away SpecialParameterGenerator instead of the accumulating
+                    // one.
+                    SpecialParameterGenerator specialGeneratorForThisSetter =
+                            KNOWN_BAD.containsEntry(instance.getClass(), setter.getName())
+                                    ? new SpecialParameterGenerator(specialGenerator.mContext)
+                                    : specialGenerator;
+
+                    Object[] setterParam = generateParameters(setter, where,
+                            excludingParameterTypes, specialGeneratorForThisSetter);
+                    Log.i(TAG, "Invoking " + ReflectionUtils.methodToString(setter) + " with "
+                            + setterParam[0]);
+                    setter.invoke(instance, setterParam);
+                }
+            } catch (Exception e) {
+                throw new UnsupportedOperationException(
+                        "Error invoking setter " + ReflectionUtils.methodToString(setter), e);
+            }
+        }
+    }
+
+    private static Object[] generateParameters(Executable executable, Location where,
+            Set<Class<?>> excludingClasses, SpecialParameterGenerator specialGenerator) {
+        Log.i(TAG, "About to generate parameters for " + ReflectionUtils.methodToString(executable)
+                + " in " + where);
+        Class<?>[] parameterTypes = executable.getParameterTypes();
+        Object[] parameterValues = new Object[parameterTypes.length];
+        for (int i = 0; i < parameterTypes.length; i++) {
+            parameterValues[i] = generateObject(
+                    parameterTypes[i],
+                    where.plus(executable,
+                            String.format("[%d,%s]", i, parameterTypes[i].getName())),
+                    excludingClasses,
+                    specialGenerator);
+        }
+        return parameterValues;
+    }
+
+    private static class ReflectionUtils {
+        static Set<Class<?>> getConcreteSubclasses(Class<?> clazz, Class<?> containerClass) {
+            return Arrays.stream(containerClass.getDeclaredClasses())
+                    .filter(
+                            innerClass -> clazz.isAssignableFrom(innerClass)
+                                    && !Modifier.isAbstract(innerClass.getModifiers()))
+                    .collect(Collectors.toSet());
+        }
+
+        static String methodToString(Executable executable) {
+            return String.format("%s::%s(%s)",
+                    executable.getDeclaringClass().getName(),
+                    executable.getName(),
+                    Arrays.stream(executable.getParameterTypes()).map(Class::getSimpleName)
+                            .collect(Collectors.joining(", "))
+            );
+        }
+
+        static List<Method> getAllSetters(Class<?> clazz, Location where, boolean allOverloads,
+                boolean includingVoidMethods, Set<Class<?>> excludingParameterTypes) {
+            ListMultimap<String, Method> methods = ArrayListMultimap.create();
+            // Candidate "setters" are any methods that receive one at least parameter and are
+            // either void (if acceptable) or return the same type being built.
+            for (Method method : clazz.getDeclaredMethods()) {
+                if (Modifier.isPublic(method.getModifiers())
+                        && !Modifier.isStatic(method.getModifiers())
+                        && method.getAnnotation(Deprecated.class) == null
+                        && ((includingVoidMethods && method.getReturnType().equals(Void.TYPE))
+                        || method.getReturnType().equals(clazz))
+                        && method.getParameterCount() >= 1
+                        && !EXCLUDED_SETTERS.containsEntry(clazz, method.getName())
+                        && Arrays.stream(method.getParameterTypes())
+                            .noneMatch(excludingParameterTypes::contains)) {
+                    methods.put(method.getName(), method);
+                }
+            }
+
+            // In case of overloads, prefer those with the most interesting parameters.
+            List<Method> setters = new ArrayList<>();
+            for (String methodName : methods.keySet()) {
+                setters.addAll(chooseOverloads(methods.get(methodName), where, allOverloads));
+            }
+
+            // Exclude set(x[]) when there exists add(x).
+            List<Method> excludedSetters = setters.stream().filter(
+                    m1 -> m1.getName().startsWith("set")
+                            && setters.stream().anyMatch(
+                                    m2 -> {
+                                            Class<?> param1 = m1.getParameterTypes()[0];
+                                            Class<?> param2 = m2.getParameterTypes()[0];
+                                            return m2.getName().startsWith("add")
+                                                    && param1.isArray()
+                                                    && !param2.isArray() && !param2.isPrimitive()
+                                                    && param1.getComponentType().equals(param2);
+                                    })).toList();
+
+            setters.removeAll(excludedSetters);
+            return setters;
+        }
+
+        @Nullable
+        static <T extends Executable> T chooseBestOverload(List<T> executables, Location where) {
+            ImmutableList<T> chosen = chooseOverloads(executables, where,
+                    /* chooseMultiple= */ false);
+            return (chosen.isEmpty() ? null : chosen.get(0));
+        }
+
+        static <T extends Executable> ImmutableList<T> chooseOverloads(List<T> executables,
+                Location where, boolean chooseMultiple) {
+            // Exclude variants with non-usable parameters and too-deep recursions.
+            executables = executables.stream()
+                    .filter(e -> Arrays.stream(e.getParameterTypes()).noneMatch(
+                            p -> UNUSABLE_TYPES.contains(p)
+                                    || where.getClassOccurrenceCount(p) >= MAX_RECURSION))
+                    .collect(Collectors.toList());
+
+            if (executables.size() <= 1) {
+                return ImmutableList.copyOf(executables);
+            }
+
+            // Overloads in "builders" usually set the same thing in two different ways (e.g.
+            // x(Bitmap) and x(Icon)). We choose the one with the most "interesting" parameters
+            // (from the point of view of containing Uris). In case of ties, LEAST parameters win,
+            // to use the simplest.
+            ArrayList<T> sortedCopy = new ArrayList<>(executables);
+            sortedCopy.sort(
+                    Comparator.comparingInt(ReflectionUtils::getMethodScore)
+                            .thenComparing(Executable::getParameterCount)
+                            .reversed());
+
+            return chooseMultiple
+                    ? ImmutableList.copyOf(sortedCopy)
+                    : ImmutableList.of(sortedCopy.get(0));
+        }
+
+        /**
+         * Counts the number of "interesting" parameters in a method. Used to choose the constructor
+         * or builder-setter overload most suited to this test (e.g. prefer
+         * {@link Notification.Builder#setLargeIcon(Icon)} to
+         * {@link Notification.Builder#setLargeIcon(Bitmap)}.
+         */
+        static int getMethodScore(Executable executable) {
+            return Arrays.stream(executable.getParameterTypes())
+                    .mapToInt(SpecialParameterGenerator::getParameterScore).sum();
+        }
+    }
+
+    private static class SpecialParameterGenerator {
+        private static final ImmutableSet<Class<?>> INTERESTING_CLASSES =
+                ImmutableSet.of(
+                        Person.class, Uri.class, Icon.class, Intent.class, PendingIntent.class,
+                        RemoteViews.class);
+        private static final ImmutableSet<Class<?>> MOCKED_CLASSES = ImmutableSet.of();
+
+        private static final ImmutableMap<Class<?>, Object> PRIMITIVE_VALUES =
+                ImmutableMap.<Class<?>, Object>builder()
+                        .put(boolean.class, false)
+                        .put(byte.class, (byte) 4)
+                        .put(short.class, (short) 44)
+                        .put(int.class, 1)
+                        .put(long.class, 44444444L)
+                        .put(float.class, 33.33f)
+                        .put(double.class, 3333.3333d)
+                        .put(char.class, 'N')
+                        .build();
+
+        private final Context mContext;
+        private final List<Uri> mGeneratedUris = new ArrayList<>();
+        private int mNextUriCounter = 1;
+
+        SpecialParameterGenerator(Context context) {
+            mContext = context;
+        }
+
+        static boolean canGenerate(Class<?> clazz) {
+            return (INTERESTING_CLASSES.contains(clazz) && !clazz.equals(Person.class))
+                    || MOCKED_CLASSES.contains(clazz)
+                    || clazz.equals(Context.class)
+                    || clazz.equals(Bundle.class)
+                    || clazz.equals(Bitmap.class)
+                    || clazz.isPrimitive()
+                    || clazz.equals(CharSequence.class) || clazz.equals(String.class);
+        }
+
+        static int getParameterScore(Class<?> parameterClazz) {
+            if (parameterClazz.isArray()) {
+                return getParameterScore(parameterClazz.getComponentType());
+            } else if (INTERESTING_CLASSES.contains(parameterClazz)) {
+                return 10;
+            } else if (parameterClazz.isPrimitive() || parameterClazz.equals(CharSequence.class)
+                    || parameterClazz.equals(String.class)) {
+                return 0;
+            } else {
+                // No idea. We don't deep inspect, but score them as better than known-useless.
+                return 1;
+            }
+        }
+
+        Object generate(Class<?> clazz, Location where) {
+            if (clazz == Uri.class) {
+                return generateUri(where);
+            }
+
+            // Interesting parameters
+            if (clazz == Icon.class) {
+                Uri iconUri = generateUri(
+                        where.plus(Icon.class).plus("createWithContentUri", Uri.class));
+                return Icon.createWithContentUri(iconUri);
+            }
+
+            if (clazz == Intent.class) {
+                // TODO(b/281044385): Are Intent Uris (new Intent(String,Uri)) relevant?
+                return new Intent("action");
+            }
+
+            if (clazz == PendingIntent.class) {
+                // PendingIntent can have an Intent with a Uri but those are inaccessible and
+                // not inspected.
+                return PendingIntent.getActivity(mContext, 0, new Intent("action"),
+                        PendingIntent.FLAG_IMMUTABLE);
+            }
+
+            if (clazz == RemoteViews.class) {
+                RemoteViews rv = new RemoteViews(mContext.getPackageName(), /* layoutId= */ 10);
+                invokeAllSetters(rv, where.plus(RemoteViews.class),
+                        /* allOverloads= */ true, /* includingVoidMethods= */ true,
+                        /* excludingParameterTypes= */ ImmutableSet.of(), this);
+                return rv;
+            }
+
+            if (MOCKED_CLASSES.contains(clazz)) {
+                return Mockito.mock(clazz);
+            }
+            if (clazz.equals(Context.class)) {
+                return mContext;
+            }
+            if (clazz.equals(Bundle.class)) {
+                return new Bundle();
+            }
+            if (clazz.equals(Bitmap.class)) {
+                return Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888);
+            }
+
+            // ~Primitives
+            if (PRIMITIVE_VALUES.containsKey(clazz)) {
+                return PRIMITIVE_VALUES.get(clazz);
+            }
+            if (clazz.equals(CharSequence.class) || clazz.equals(String.class)) {
+                return where + "->string";
+            }
+
+            throw new IllegalArgumentException(
+                    "I have no idea how to produce a(n) " + clazz + ", sorry");
+        }
+
+        private Uri generateUri(Location where) {
+            Uri uri = Uri.parse(String.format("%s - %s", mNextUriCounter++, where));
+            mGeneratedUris.add(uri);
+            return uri;
+        }
+
+        public List<Uri> getGeneratedUris() {
+            return mGeneratedUris;
+        }
+    }
+
+    private static class Location {
+
+        private static class Item {
+            @Nullable private final Class<?> mMaybeClass;
+            @Nullable private final Executable mMaybeMethod;
+            @Nullable private final String mExtra;
+
+            Item(@NonNull Class<?> clazz) {
+                mMaybeClass = checkNotNull(clazz);
+                mMaybeMethod = null;
+                mExtra = null;
+            }
+
+            Item(@NonNull Executable executable, @Nullable String extra) {
+                mMaybeClass = null;
+                mMaybeMethod = checkNotNull(executable);
+                mExtra = extra;
+            }
+
+            @NonNull
+            @Override
+            public String toString() {
+                String name = mMaybeClass != null
+                        ? "CLASS:" + mMaybeClass.getName()
+                        : "METHOD:" + mMaybeMethod.getName() + "/"
+                                + mMaybeMethod.getParameterCount();
+                return name + Strings.nullToEmpty(mExtra);
+            }
+        }
+
+        private final ImmutableList<Item> mComponents;
+
+        private Location(Iterable<Item> components) {
+            mComponents = ImmutableList.copyOf(components);
+        }
+
+        private Location(Location soFar, Item next) {
+            // Verify the class->method->class->method ordering.
+            if (!soFar.mComponents.isEmpty()) {
+                Item previous = soFar.getLastItem();
+                if (previous.mMaybeMethod != null && next.mMaybeMethod != null) {
+                    throw new IllegalArgumentException(
+                            String.format("Unexpected sequence: %s ===> %s", soFar, next));
+                }
+            }
+            mComponents = ImmutableList.<Item>builder().addAll(soFar.mComponents).add(next).build();
+        }
+
+        public static Location root(Class<?> clazz) {
+            return new Location(ImmutableList.of(new Item(clazz)));
+        }
+
+        Location plus(Class<?> clazz) {
+            return new Location(this, new Item(clazz));
+        }
+
+        Location plus(Executable executable, String extra) {
+            return new Location(this, new Item(executable, extra));
+        }
+
+        public Location plus(String methodName, Class<?>... methodParameters) {
+            Item lastClass = getLastItem();
+            try {
+                checkNotNull(lastClass.mMaybeClass, "Last item is not a class but %s", lastClass);
+                Method method = lastClass.mMaybeClass.getMethod(methodName, methodParameters);
+                return new Location(this, new Item(method, null));
+            } catch (NoSuchMethodException e) {
+                throw new IllegalArgumentException(
+                        String.format("Method %s not found in class %s",
+                                methodName, lastClass.mMaybeClass.getName()));
+            }
+        }
+
+        Item getLastItem() {
+            checkState(!mComponents.isEmpty());
+            return mComponents.get(mComponents.size() - 1);
+        }
+
+        @NonNull
+        @Override
+        public String toString() {
+            return mComponents.stream().map(Item::toString).collect(Collectors.joining(" -> "));
+        }
+
+        public long getClassOccurrenceCount(Class<?> clazz) {
+            return mComponents.stream().filter(c -> clazz.equals(c.mMaybeClass)).count();
+        }
+    }
+
+    private static class Generated<T> {
+        public final T value;
+        public final ImmutableList<Uri> includedUris;
+
+        private Generated(T value, Iterable<Uri> includedUris) {
+            this.value = value;
+            this.includedUris = ImmutableList.copyOf(includedUris);
+        }
+    }
+}
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java
index 385c28a..6a1674b 100644
--- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java
@@ -38,7 +38,6 @@
 import android.os.Process;
 import android.os.RemoteException;
 
-import androidx.test.filters.FlakyTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.internal.util.FakeLatencyTracker;
@@ -92,12 +91,10 @@
     }
 
     @Test
-    @FlakyTest(bugId = 275113847)
     public void testSetUpAndTearDown() {
     }
 
     @Test
-    @FlakyTest(bugId = 275113847)
     public void testOnPhraseRecognitionStartsLatencyTrackerWithSuccessfulPhraseIdTrigger()
             throws RemoteException {
         ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass(
@@ -114,7 +111,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 275113847)
     public void testOnPhraseRecognitionRestartsActiveSession() throws RemoteException {
         ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass(
                 ISoundTriggerCallback.class);
@@ -135,7 +131,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 275113847)
     public void testOnPhraseRecognitionNeverStartsLatencyTrackerWithNonSuccessEvent()
             throws RemoteException {
         ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass(
@@ -153,7 +148,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 275113847)
     public void testOnPhraseRecognitionNeverStartsLatencyTrackerWithNoKeyphraseId()
             throws RemoteException {
         ArgumentCaptor<ISoundTriggerCallback> soundTriggerCallbackCaptor = ArgumentCaptor.forClass(
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
index bf6901e..9029bc4 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
@@ -18,6 +18,7 @@
 
 import static android.view.KeyEvent.KEYCODE_ALT_LEFT;
 import static android.view.KeyEvent.KEYCODE_B;
+import static android.view.KeyEvent.KEYCODE_BRIGHTNESS_DOWN;
 import static android.view.KeyEvent.KEYCODE_C;
 import static android.view.KeyEvent.KEYCODE_CTRL_LEFT;
 import static android.view.KeyEvent.KEYCODE_E;
@@ -186,4 +187,19 @@
         sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_ENTER}, 0);
         mPhoneWindowManager.assertGoToHomescreen();
     }
+
+    /**
+     * Sends a KEYCODE_BRIGHTNESS_DOWN event and validates the brightness is decreased as expected;
+     */
+    @Test
+    public void testKeyCodeBrightnessDown() {
+        float[] currentBrightness = new float[]{0.1f, 0.05f, 0.0f};
+        float[] newBrightness = new float[]{0.065738f, 0.0275134f, 0.0f};
+
+        for (int i = 0; i < currentBrightness.length; i++) {
+            mPhoneWindowManager.prepareBrightnessDecrease(currentBrightness[i]);
+            sendKey(KEYCODE_BRIGHTNESS_DOWN);
+            mPhoneWindowManager.verifyNewBrightness(newBrightness[i]);
+        }
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 1053fd5..d383024 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -89,6 +89,7 @@
 
 import junit.framework.Assert;
 
+import org.mockito.AdditionalMatchers;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockSettings;
@@ -339,6 +340,20 @@
         setPhoneCallIsInProgress();
     }
 
+    void prepareBrightnessDecrease(float currentBrightness) {
+        doReturn(0.0f).when(mPowerManager)
+                .getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM);
+        doReturn(1.0f).when(mPowerManager)
+                .getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM);
+        doReturn(currentBrightness).when(mDisplayManager)
+                .getBrightness(0);
+    }
+
+    void verifyNewBrightness(float newBrightness) {
+        verify(mDisplayManager).setBrightness(Mockito.eq(0),
+                AdditionalMatchers.eq(newBrightness, 0.001f));
+    }
+
     void setPhoneCallIsInProgress() {
         // Let device has an ongoing phone call.
         doReturn(false).when(mTelecomManager).isRinging();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 7330411..340b591 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -53,6 +53,8 @@
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -2827,6 +2829,26 @@
                 mDisplayContent.getKeepClearAreas());
     }
 
+    @Test
+    public void testMayImeShowOnLaunchingActivity_negativeWhenSoftInputModeHidden() {
+        final ActivityRecord app = createActivityRecord(mDisplayContent);
+        final WindowState appWin = createWindow(null, TYPE_BASE_APPLICATION, app, "appWin");
+        createWindow(null, TYPE_APPLICATION_STARTING, app, "startingWin");
+        app.mStartingData = mock(SnapshotStartingData.class);
+        // Assume the app has shown IME before and warm launching with a snapshot window.
+        doReturn(true).when(app.mStartingData).hasImeSurface();
+
+        // Expect true when this IME focusable activity will show IME during launching.
+        assertTrue(WindowManager.LayoutParams.mayUseInputMethod(appWin.mAttrs.flags));
+        assertTrue(mDisplayContent.mayImeShowOnLaunchingActivity(app));
+
+        // Not expect IME will be shown during launching if the app's softInputMode is hidden.
+        appWin.mAttrs.softInputMode = SOFT_INPUT_STATE_ALWAYS_HIDDEN;
+        assertFalse(mDisplayContent.mayImeShowOnLaunchingActivity(app));
+        appWin.mAttrs.softInputMode = SOFT_INPUT_STATE_HIDDEN;
+        assertFalse(mDisplayContent.mayImeShowOnLaunchingActivity(app));
+    }
+
     private void removeRootTaskTests(Runnable runnable) {
         final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
         final Task rootTask1 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index 353a8ec..8ac6b0f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -22,6 +22,7 @@
 import static android.view.Surface.ROTATION_0;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.ViewRootImpl.CLIENT_TRANSIENT;
 import static android.view.WindowInsets.Type.navigationBars;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
@@ -63,6 +64,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -388,6 +390,7 @@
     @SetupWindows(addWindows = { W_ACTIVITY, W_NAVIGATION_BAR })
     @Test
     public void testCanSystemBarsBeShownByUser() {
+        Assume.assumeFalse(CLIENT_TRANSIENT);
         ((TestWindowManagerPolicy) mWm.mPolicy).mIsUserSetupComplete = true;
         mAppWindow.mAttrs.insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
         mAppWindow.setRequestedVisibleTypes(0, navigationBars());
@@ -409,6 +412,7 @@
     @UseTestDisplay(addWindows = { W_NAVIGATION_BAR })
     @Test
     public void testTransientBarsSuppressedOnDreams() {
+        Assume.assumeFalse(CLIENT_TRANSIENT);
         final WindowState win = createDreamWindow();
 
         ((TestWindowManagerPolicy) mWm.mPolicy).mIsUserSetupComplete = true;
diff --git a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
index a3a3684..5eebe74 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
@@ -26,6 +26,7 @@
 import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 import static com.android.server.wm.WindowContainer.SYNC_STATE_NONE;
+import static com.android.server.wm.WindowContainer.SYNC_STATE_READY;
 import static com.android.server.wm.WindowState.BLAST_TIMEOUT_DURATION;
 
 import static org.junit.Assert.assertEquals;
@@ -38,7 +39,9 @@
 import static org.mockito.Mockito.spy;
 
 import android.platform.test.annotations.Presubmit;
+import android.util.MergedConfiguration;
 import android.view.SurfaceControl;
+import android.window.ClientWindowFrames;
 
 import androidx.test.filters.SmallTest;
 
@@ -306,6 +309,19 @@
         assertEquals(SYNC_STATE_NONE, parentWC.mSyncState);
         assertEquals(SYNC_STATE_NONE, topChildWC.mSyncState);
         assertEquals(SYNC_STATE_NONE, botChildWC.mSyncState);
+
+        // If the appearance of window won't change after reparenting, its sync state can be kept.
+        final WindowState w = createWindow(null, TYPE_BASE_APPLICATION, "win");
+        parentWC.onRequestedOverrideConfigurationChanged(w.getConfiguration());
+        w.reparent(botChildWC, POSITION_TOP);
+        parentWC.prepareSync();
+        // Assume the window has drawn with the latest configuration.
+        w.fillClientWindowFramesAndConfiguration(new ClientWindowFrames(),
+                new MergedConfiguration(), true /* useLatestConfig */, true /* relayoutVisible */);
+        assertTrue(w.onSyncFinishedDrawing());
+        assertEquals(SYNC_STATE_READY, w.mSyncState);
+        w.reparent(topChildWC, POSITION_TOP);
+        assertEquals(SYNC_STATE_READY, w.mSyncState);
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 58bf184..d3f6818 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -67,7 +67,6 @@
 import static org.mockito.Mockito.when;
 
 import android.content.pm.ActivityInfo;
-import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.hardware.display.VirtualDisplay;
@@ -515,12 +514,8 @@
 
     @Test
     public void testSetInTouchMode_instrumentedProcessGetPermissionToSwitchTouchMode() {
-        // Disable global touch mode (config_perDisplayFocusEnabled set to true)
-        Resources mockResources = mock(Resources.class);
-        spyOn(mContext);
-        when(mContext.getResources()).thenReturn(mockResources);
-        doReturn(true).when(mockResources).getBoolean(
-                com.android.internal.R.bool.config_perDisplayFocusEnabled);
+        // Enable global touch mode
+        mWm.mPerDisplayFocusEnabled = true;
 
         // Get current touch mode state and setup WMS to run setInTouchMode
         boolean currentTouchMode = mWm.isInTouchMode(DEFAULT_DISPLAY);
@@ -539,12 +534,8 @@
 
     @Test
     public void testSetInTouchMode_nonInstrumentedProcessDontGetPermissionToSwitchTouchMode() {
-        // Disable global touch mode (config_perDisplayFocusEnabled set to true)
-        Resources mockResources = mock(Resources.class);
-        spyOn(mContext);
-        when(mContext.getResources()).thenReturn(mockResources);
-        doReturn(true).when(mockResources).getBoolean(
-                com.android.internal.R.bool.config_perDisplayFocusEnabled);
+        // Enable global touch mode
+        mWm.mPerDisplayFocusEnabled = true;
 
         // Get current touch mode state and setup WMS to run setInTouchMode
         boolean currentTouchMode = mWm.isInTouchMode(DEFAULT_DISPLAY);
@@ -563,6 +554,9 @@
 
     @Test
     public void testSetInTouchMode_multiDisplay_globalTouchModeUpdate() {
+        // Disable global touch mode
+        mWm.mPerDisplayFocusEnabled = false;
+
         // Create one extra display
         final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ false);
         final VirtualDisplay virtualDisplayOwnTouchMode =
@@ -570,17 +564,10 @@
         final int numberOfDisplays = mWm.mRoot.mChildren.size();
         assertThat(numberOfDisplays).isAtLeast(3);
         final int numberOfGlobalTouchModeDisplays = (int) mWm.mRoot.mChildren.stream()
-                        .filter(d -> (d.getDisplay().getFlags() & FLAG_OWN_FOCUS) == 0)
-                        .count();
+                .filter(d -> (d.getDisplay().getFlags() & FLAG_OWN_FOCUS) == 0)
+                .count();
         assertThat(numberOfGlobalTouchModeDisplays).isAtLeast(2);
 
-        // Enable global touch mode (config_perDisplayFocusEnabled set to false)
-        Resources mockResources = mock(Resources.class);
-        spyOn(mContext);
-        when(mContext.getResources()).thenReturn(mockResources);
-        doReturn(false).when(mockResources).getBoolean(
-                com.android.internal.R.bool.config_perDisplayFocusEnabled);
-
         // Get current touch mode state and setup WMS to run setInTouchMode
         boolean currentTouchMode = mWm.isInTouchMode(DEFAULT_DISPLAY);
         int callingPid = Binder.getCallingPid();
@@ -598,18 +585,14 @@
 
     @Test
     public void testSetInTouchMode_multiDisplay_perDisplayFocus_singleDisplayTouchModeUpdate() {
+        // Enable global touch mode
+        mWm.mPerDisplayFocusEnabled = true;
+
         // Create one extra display
         final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ false);
         final int numberOfDisplays = mWm.mRoot.mChildren.size();
         assertThat(numberOfDisplays).isAtLeast(2);
 
-        // Disable global touch mode (config_perDisplayFocusEnabled set to true)
-        Resources mockResources = mock(Resources.class);
-        spyOn(mContext);
-        when(mContext.getResources()).thenReturn(mockResources);
-        doReturn(true).when(mockResources).getBoolean(
-                com.android.internal.R.bool.config_perDisplayFocusEnabled);
-
         // Get current touch mode state and setup WMS to run setInTouchMode
         boolean currentTouchMode = mWm.isInTouchMode(DEFAULT_DISPLAY);
         int callingPid = Binder.getCallingPid();
@@ -628,18 +611,14 @@
 
     @Test
     public void testSetInTouchMode_multiDisplay_ownTouchMode_singleDisplayTouchModeUpdate() {
+        // Disable global touch mode
+        mWm.mPerDisplayFocusEnabled = false;
+
         // Create one extra display
         final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ true);
         final int numberOfDisplays = mWm.mRoot.mChildren.size();
         assertThat(numberOfDisplays).isAtLeast(2);
 
-        // Enable global touch mode (config_perDisplayFocusEnabled set to false)
-        Resources mockResources = mock(Resources.class);
-        spyOn(mContext);
-        when(mContext.getResources()).thenReturn(mockResources);
-        doReturn(false).when(mockResources).getBoolean(
-                com.android.internal.R.bool.config_perDisplayFocusEnabled);
-
         // Get current touch mode state and setup WMS to run setInTouchMode
         boolean currentTouchMode = mWm.isInTouchMode(DEFAULT_DISPLAY);
         int callingPid = Binder.getCallingPid();
@@ -667,19 +646,14 @@
     }
 
     private void testSetInTouchModeOnAllDisplays(boolean perDisplayFocusEnabled) {
+        // Set global touch mode with the value passed as argument.
+        mWm.mPerDisplayFocusEnabled = perDisplayFocusEnabled;
+
         // Create a couple of extra displays.
         // setInTouchModeOnAllDisplays should ignore the ownFocus setting.
         final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ false);
         final VirtualDisplay virtualDisplayOwnFocus = createVirtualDisplay(/* ownFocus= */ true);
 
-        // Enable or disable global touch mode (config_perDisplayFocusEnabled setting).
-        // setInTouchModeOnAllDisplays should ignore this value.
-        Resources mockResources = mock(Resources.class);
-        spyOn(mContext);
-        when(mContext.getResources()).thenReturn(mockResources);
-        doReturn(perDisplayFocusEnabled).when(mockResources).getBoolean(
-                com.android.internal.R.bool.config_perDisplayFocusEnabled);
-
         int callingPid = Binder.getCallingPid();
         int callingUid = Binder.getCallingUid();
         doReturn(false).when(mWm).checkCallingPermission(anyString(), anyString(), anyBoolean());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index a4cad5e..863523f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -197,6 +197,8 @@
      */
     private static boolean sOverridesCheckedTestDisplay;
 
+    private boolean mOriginalPerDisplayFocusEnabled;
+
     @BeforeClass
     public static void setUpOnceBase() {
         AttributeCache.init(getInstrumentation().getTargetContext());
@@ -208,6 +210,7 @@
         mSupervisor = mAtm.mTaskSupervisor;
         mRootWindowContainer = mAtm.mRootWindowContainer;
         mWm = mSystemServicesTestRule.getWindowManagerService();
+        mOriginalPerDisplayFocusEnabled = mWm.mPerDisplayFocusEnabled;
         SystemServicesTestRule.checkHoldsLock(mWm.mGlobalLock);
 
         mDefaultDisplay = mWm.mRoot.getDefaultDisplay();
@@ -279,6 +282,7 @@
         if (mUseFakeSettingsProvider) {
             FakeSettingsProvider.clearSettingsProvider();
         }
+        mWm.mPerDisplayFocusEnabled = mOriginalPerDisplayFocusEnabled;
     }
 
     /**
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
index 99881e1..fd0d540 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -89,6 +89,7 @@
     private static final int USB_VENDORID_SONY = 0x054C;
     private static final int USB_PRODUCTID_PS4CONTROLLER_ZCT1 = 0x05C4;
     private static final int USB_PRODUCTID_PS4CONTROLLER_ZCT2 = 0x09CC;
+    private static final int USB_PRODUCTID_PS5CONTROLLER = 0x0CE6;
 
     private static final int USB_DENYLIST_OUTPUT = 0x0001;
     private static final int USB_DENYLIST_INPUT  = 0x0002;
@@ -111,6 +112,9 @@
                     USB_DENYLIST_OUTPUT),
             new DenyListEntry(USB_VENDORID_SONY,
                     USB_PRODUCTID_PS4CONTROLLER_ZCT2,
+                    USB_DENYLIST_OUTPUT),
+            new DenyListEntry(USB_VENDORID_SONY,
+                    USB_PRODUCTID_PS5CONTROLLER,
                     USB_DENYLIST_OUTPUT));
 
     private static boolean isDeviceDenylisted(int vendorId, int productId, int flags) {
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index 00d74bf..f1e1a5a 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -119,6 +119,9 @@
     @GuardedBy("mLock")
     private SoundTriggerDeviceState mDeviceState = SoundTriggerDeviceState.DISABLE;
 
+    @GuardedBy("mLock")
+    private boolean mIsAppOpPermitted = true;
+
     SoundTriggerHelper(Context context, EventLogger eventLogger,
             @NonNull Function<SoundTrigger.StatusListener, SoundTriggerModule> moduleProvider,
             int moduleId,
@@ -323,7 +326,7 @@
             modelData.setRunInBatterySaverMode(runInBatterySaverMode);
             modelData.setSoundModel(soundModel);
 
-            if (isRecognitionAllowedByDeviceState(modelData)) {
+            if (isRecognitionAllowed(modelData)) {
                 int startRecoResult = updateRecognitionLocked(modelData,
                         false /* Don't notify for synchronous calls */);
                 if (startRecoResult == SoundTrigger.STATUS_OK) {
@@ -613,6 +616,16 @@
         }
     }
 
+    public void onAppOpStateChanged(boolean isPermitted) {
+        synchronized (mLock) {
+            if (mIsAppOpPermitted == isPermitted) {
+                return;
+            }
+            mIsAppOpPermitted = isPermitted;
+            updateAllRecognitionsLocked();
+        }
+    }
+
     public int getGenericModelState(UUID modelId) {
         synchronized (mLock) {
             MetricsLogger.count(mContext, "sth_get_generic_model_state", 1);
@@ -782,6 +795,7 @@
         return event instanceof KeyphraseRecognitionEvent;
     }
 
+    @GuardedBy("mLock")
     private void onGenericRecognitionLocked(GenericRecognitionEvent event) {
         MetricsLogger.count(mContext, "sth_generic_recognition_event", 1);
         if (event.status != SoundTrigger.RECOGNITION_STATUS_SUCCESS
@@ -789,15 +803,15 @@
             return;
         }
         ModelData model = getModelDataForLocked(event.soundModelHandle);
-        if (!Objects.equals(event.getToken(), model.getToken())) {
-            // Stale event, do nothing
-            return;
-        }
         if (model == null || !model.isGenericModel()) {
             Slog.w(TAG, "Generic recognition event: Model does not exist for handle: "
                     + event.soundModelHandle);
             return;
         }
+        if (!Objects.equals(event.getToken(), model.getToken())) {
+            // Stale event, do nothing
+            return;
+        }
 
         IRecognitionStatusCallback callback = model.getCallback();
         if (callback == null) {
@@ -866,6 +880,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     private void onResourcesAvailableLocked() {
         mEventLogger.enqueue(new SessionEvent(Type.RESOURCES_AVAILABLE, null));
         updateAllRecognitionsLocked();
@@ -875,11 +890,11 @@
         Slog.w(TAG, "Recognition aborted");
         MetricsLogger.count(mContext, "sth_recognition_aborted", 1);
         ModelData modelData = getModelDataForLocked(event.soundModelHandle);
-        if (!Objects.equals(event.getToken(), modelData.getToken())) {
-            // Stale event, do nothing
-            return;
-        }
         if (modelData != null && modelData.isModelStarted()) {
+            if (!Objects.equals(event.getToken(), modelData.getToken())) {
+                // Stale event, do nothing
+                return;
+            }
             modelData.setStopped();
             try {
                 IRecognitionStatusCallback callback = modelData.getCallback();
@@ -911,21 +926,21 @@
         return keyphraseExtras[0].id;
     }
 
+    @GuardedBy("mLock")
     private void onKeyphraseRecognitionLocked(KeyphraseRecognitionEvent event) {
         Slog.i(TAG, "Recognition success");
         MetricsLogger.count(mContext, "sth_keyphrase_recognition_event", 1);
         int keyphraseId = getKeyphraseIdFromEvent(event);
         ModelData modelData = getKeyphraseModelDataLocked(keyphraseId);
-        if (!Objects.equals(event.getToken(), modelData.getToken())) {
-            // Stale event, do nothing
-            return;
-        }
 
         if (modelData == null || !modelData.isKeyphraseModel()) {
             Slog.e(TAG, "Keyphase model data does not exist for ID:" + keyphraseId);
             return;
         }
-
+        if (!Objects.equals(event.getToken(), modelData.getToken())) {
+            // Stale event, do nothing
+            return;
+        }
         if (modelData.getCallback() == null) {
             Slog.w(TAG, "Received onRecognition event without callback for keyphrase model.");
             return;
@@ -957,6 +972,7 @@
         }
     }
 
+    @GuardedBy("mLock")
     private void updateAllRecognitionsLocked() {
         // updateRecognitionLocked can possibly update the list of models
         ArrayList<ModelData> modelDatas = new ArrayList<ModelData>(mModelDataMap.values());
@@ -965,8 +981,9 @@
         }
     }
 
+    @GuardedBy("mLock")
     private int updateRecognitionLocked(ModelData model, boolean notifyClientOnError) {
-        boolean shouldStartModel = model.isRequested() && isRecognitionAllowedByDeviceState(model);
+        boolean shouldStartModel = model.isRequested() && isRecognitionAllowed(model);
         if (shouldStartModel == model.isModelStarted()) {
             // No-op.
             return STATUS_OK;
@@ -1185,7 +1202,10 @@
      * @return True if recognition is allowed to run at this time. False if not.
      */
     @GuardedBy("mLock")
-    private boolean isRecognitionAllowedByDeviceState(ModelData modelData) {
+    private boolean isRecognitionAllowed(ModelData modelData) {
+        if (!mIsAppOpPermitted) {
+            return false;
+        }
         return switch (mDeviceState) {
             case DISABLE -> false;
             case CRITICAL -> modelData.shouldRunInBatterySaverMode();
@@ -1196,6 +1216,7 @@
 
     // A single routine that implements the start recognition logic for both generic and keyphrase
     // models.
+    @GuardedBy("mLock")
     private int startRecognitionLocked(ModelData modelData, boolean notifyClientOnError) {
         IRecognitionStatusCallback callback = modelData.getCallback();
         RecognitionConfig config = modelData.getRecognitionConfig();
@@ -1206,7 +1227,7 @@
             return STATUS_ERROR;
         }
 
-        if (!isRecognitionAllowedByDeviceState(modelData)) {
+        if (!isRecognitionAllowed(modelData)) {
             // Nothing to do here.
             Slog.w(TAG, "startRecognition requested but not allowed.");
             MetricsLogger.count(mContext, "sth_start_recognition_not_allowed", 1);
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index a675248..9fb5509 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -42,6 +42,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityThread;
+import android.app.AppOpsManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -99,6 +100,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.ISoundTriggerService;
 import com.android.internal.app.ISoundTriggerSession;
+import com.android.internal.util.DumpUtils;
 import com.android.server.SoundTriggerInternal;
 import com.android.server.SystemService;
 import com.android.server.soundtrigger.SoundTriggerEvent.ServiceEvent;
@@ -110,6 +112,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.function.Consumer;
 import java.util.List;
 import java.util.Set;
 import java.util.Deque;
@@ -235,6 +238,8 @@
     private final DeviceStateHandler mDeviceStateHandler;
     private final Executor mDeviceStateHandlerExecutor = Executors.newSingleThreadExecutor();
     private PhoneCallStateHandler mPhoneCallStateHandler;
+    private AppOpsManager mAppOpsManager;
+    private PackageManager mPackageManager;
 
     public SoundTriggerService(Context context) {
         super(context);
@@ -257,6 +262,8 @@
         Slog.d(TAG, "onBootPhase: " + phase + " : " + isSafeMode());
         if (PHASE_THIRD_PARTY_APPS_CAN_START == phase) {
             mDbHelper = new SoundTriggerDbHelper(mContext);
+            mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
+            mPackageManager = mContext.getPackageManager();
             final PowerManager powerManager = mContext.getSystemService(PowerManager.class);
             // Hook up power state listener
             mContext.registerReceiver(
@@ -348,6 +355,44 @@
         }
     }
 
+    class MyAppOpsListener implements AppOpsManager.OnOpChangedListener {
+        private final Identity mOriginatorIdentity;
+        private final Consumer<Boolean> mOnOpModeChanged;
+
+        MyAppOpsListener(Identity originatorIdentity, Consumer<Boolean> onOpModeChanged) {
+            mOriginatorIdentity = Objects.requireNonNull(originatorIdentity);
+            mOnOpModeChanged = Objects.requireNonNull(onOpModeChanged);
+            // Validate package name
+            try {
+                int uid = mPackageManager.getPackageUid(mOriginatorIdentity.packageName,
+                        PackageManager.PackageInfoFlags.of(0));
+                if (uid != mOriginatorIdentity.uid) {
+                    throw new SecurityException("Package name: " +
+                            mOriginatorIdentity.packageName + "with uid: " + uid
+                            + "attempted to spoof as: " + mOriginatorIdentity.uid);
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                throw new SecurityException("Package name not found: "
+                        + mOriginatorIdentity.packageName);
+            }
+        }
+
+        @Override
+        public void onOpChanged(String op, String packageName) {
+            if (!Objects.equals(op, AppOpsManager.OPSTR_RECORD_AUDIO)) {
+                return;
+            }
+            final int mode = mAppOpsManager.checkOpNoThrow(
+                    AppOpsManager.OPSTR_RECORD_AUDIO, mOriginatorIdentity.uid,
+                    mOriginatorIdentity.packageName);
+            mOnOpModeChanged.accept(mode == AppOpsManager.MODE_ALLOWED);
+        }
+
+        void forceOpChangeRefresh() {
+            onOpChanged(AppOpsManager.OPSTR_RECORD_AUDIO, mOriginatorIdentity.packageName);
+        }
+    }
+
     class SoundTriggerServiceStub extends ISoundTriggerService.Stub {
         @Override
         public ISoundTriggerSession attachAsOriginator(@NonNull Identity originatorIdentity,
@@ -424,6 +469,7 @@
 
         @Override
         public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
             // Event loggers
             pw.println("##Service-Wide logs:");
             mServiceEventLogger.dump(pw, /* indent = */ "  ");
@@ -461,6 +507,7 @@
         private final Object mCallbacksLock = new Object();
         private final TreeMap<UUID, IRecognitionStatusCallback> mCallbacks = new TreeMap<>();
         private final EventLogger mEventLogger;
+        private final MyAppOpsListener mAppOpsListener;
 
         SoundTriggerSessionStub(@NonNull IBinder client,
                 SoundTriggerHelper soundTriggerHelper, EventLogger eventLogger) {
@@ -477,6 +524,12 @@
             }
             mListener = (SoundTriggerDeviceState state)
                     -> mSoundTriggerHelper.onDeviceStateChanged(state);
+            mAppOpsListener = new MyAppOpsListener(mOriginatorIdentity,
+                    mSoundTriggerHelper::onAppOpStateChanged);
+            mAppOpsListener.forceOpChangeRefresh();
+            mAppOpsManager.startWatchingMode(AppOpsManager.OPSTR_RECORD_AUDIO,
+                    mOriginatorIdentity.packageName, AppOpsManager.WATCH_FOREGROUND_CHANGES,
+                    mAppOpsListener);
             mDeviceStateHandler.registerListener(mListener);
         }
 
@@ -928,6 +981,9 @@
         }
 
         private void detach() {
+            if (mAppOpsListener != null) {
+                mAppOpsManager.stopWatchingMode(mAppOpsListener);
+            }
             mDeviceStateHandler.unregisterListener(mListener);
             mSoundTriggerHelper.detach();
             detachSessionLogger(mEventLogger);
@@ -943,9 +999,8 @@
         }
 
         private void enforceDetectionPermissions(ComponentName detectionService) {
-            PackageManager packageManager = mContext.getPackageManager();
             String packageName = detectionService.getPackageName();
-            if (packageManager.checkPermission(
+            if (mPackageManager.checkPermission(
                         Manifest.permission.CAPTURE_AUDIO_HOTWORD, packageName)
                     != PackageManager.PERMISSION_GRANTED) {
                 throw new SecurityException(detectionService.getPackageName() + " does not have"
@@ -1633,6 +1688,7 @@
             private final EventLogger mEventLogger;
             private final Identity mOriginatorIdentity;
             private final @NonNull DeviceStateListener mListener;
+            private final MyAppOpsListener mAppOpsListener;
 
             private final SparseArray<UUID> mModelUuid = new SparseArray<>(1);
 
@@ -1653,6 +1709,12 @@
                 }
                 mListener = (SoundTriggerDeviceState state)
                         -> mSoundTriggerHelper.onDeviceStateChanged(state);
+                mAppOpsListener = new MyAppOpsListener(mOriginatorIdentity,
+                        mSoundTriggerHelper::onAppOpStateChanged);
+                mAppOpsListener.forceOpChangeRefresh();
+                mAppOpsManager.startWatchingMode(AppOpsManager.OPSTR_RECORD_AUDIO,
+                        mOriginatorIdentity.packageName, AppOpsManager.WATCH_FOREGROUND_CHANGES,
+                        mAppOpsListener);
                 mDeviceStateHandler.registerListener(mListener);
             }
 
@@ -1720,6 +1782,9 @@
             }
 
             private void detachInternal() {
+                if (mAppOpsListener != null) {
+                    mAppOpsManager.stopWatchingMode(mAppOpsListener);
+                }
                 mEventLogger.enqueue(new SessionEvent(Type.DETACH, null));
                 detachSessionLogger(mEventLogger);
                 mDeviceStateHandler.unregisterListener(mListener);
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
index 083211c..f70268e 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -487,10 +487,11 @@
                     if (mRecognitionToken == null) {
                         return;
                     }
+                    event.token = mRecognitionToken;
                     if (!event.recognitionEvent.recognitionStillActive) {
                         setState(ModelState.LOADED);
+                        mRecognitionToken = null;
                     }
-                    event.token = mRecognitionToken;
                     callback = mCallback;
                 }
                 // The callback must be invoked outside of the lock.
@@ -512,10 +513,11 @@
                     if (mRecognitionToken == null) {
                         return;
                     }
+                    event.token = mRecognitionToken;
                     if (!event.phraseRecognitionEvent.common.recognitionStillActive) {
                         setState(ModelState.LOADED);
+                        mRecognitionToken = null;
                     }
-                    event.token = mRecognitionToken;
                     callback = mCallback;
                 }
                 // The callback must be invoked outside of the lock.
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index c4a501d..2a6099a 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -13297,6 +13297,29 @@
     }
 
     /**
+     * Test API to verify carrier restriction status allow list i.e.
+     * packages/services/Telephony/assets/CarrierRestrictionOperatorDetails.json.
+     *
+     * @param pkgName : packaga name of the entry to verify
+     * @param carrierId : carrier Id of the entry
+     * @return {@code List<String>} : list of registered shaIds
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public List<String> getShaIdFromAllowList(String pkgName, int carrierId) {
+        try {
+            ITelephony service = getITelephony();
+            if (service != null) {
+                return service.getShaIdFromAllowList(pkgName, carrierId);
+            }
+        } catch (RemoteException ex) {
+            Rlog.e(TAG, "getShaIdFromAllowList: RemoteException = " + ex);
+            throw ex.rethrowAsRuntimeException();
+        }
+        return Collections.EMPTY_LIST;
+    }
+
+    /**
      * Used to enable or disable carrier data by the system based on carrier signalling or
      * carrier privileged apps. Different from {@link #setDataEnabled(boolean)} which is linked to
      * user settings, carrier data on/off won't affect user settings but will bypass the
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index fb46ff9..2021ac7 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -278,6 +278,11 @@
      */
     public static final int SATELLITE_REQUEST_IN_PROGRESS = 21;
 
+    /**
+     * Satellite modem is currently busy due to which current request cannot be processed.
+     */
+    public static final int SATELLITE_MODEM_BUSY = 22;
+
     /** @hide */
     @IntDef(prefix = {"SATELLITE_"}, value = {
             SATELLITE_ERROR_NONE,
@@ -301,7 +306,8 @@
             SATELLITE_NOT_REACHABLE,
             SATELLITE_NOT_AUTHORIZED,
             SATELLITE_NOT_SUPPORTED,
-            SATELLITE_REQUEST_IN_PROGRESS
+            SATELLITE_REQUEST_IN_PROGRESS,
+            SATELLITE_MODEM_BUSY
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface SatelliteError {}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 21aad73..23f4217 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -3037,4 +3037,11 @@
      * @return {@code true} if the timeout duration is set successfully, {@code false} otherwise.
      */
     boolean setSatelliteDeviceAlignedTimeoutDuration(long timeoutMillis);
+
+    /**
+     * Test method to confirm the file contents are not altered.
+     */
+     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+                 + "android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)")
+     List<String> getShaIdFromAllowList(String pkgName, int carrierId);
 }