Merge "Fix NPE in StackScrollAlgorithm.isCyclingIn/Out" into main
diff --git a/Android.bp b/Android.bp
index d6b303f..af312bf 100644
--- a/Android.bp
+++ b/Android.bp
@@ -425,6 +425,7 @@
         "sounddose-aidl-java",
         "modules-utils-expresslog",
         "perfetto_trace_javastream_protos_jarjar",
+        "libaconfig_java_proto_nano",
     ],
 }
 
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index 13d6ae5..6cfd2e0 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -44,8 +44,8 @@
             removed_api_file: ":non-updatable-removed.txt",
         },
         last_released: {
-            api_file: ":android-non-updatable.api.public.latest",
-            removed_api_file: ":android-non-updatable-removed.api.public.latest",
+            api_file: ":android-non-updatable.api.combined.public.latest",
+            removed_api_file: ":android-non-updatable-removed.api.combined.public.latest",
             baseline_file: ":android-non-updatable-incompatibilities.api.public.latest",
         },
         api_lint: {
@@ -124,8 +124,8 @@
             removed_api_file: ":non-updatable-system-removed.txt",
         },
         last_released: {
-            api_file: ":android-non-updatable.api.system.latest",
-            removed_api_file: ":android-non-updatable-removed.api.system.latest",
+            api_file: ":android-non-updatable.api.combined.system.latest",
+            removed_api_file: ":android-non-updatable-removed.api.combined.system.latest",
             baseline_file: ":android-non-updatable-incompatibilities.api.system.latest",
         },
         api_lint: {
@@ -263,8 +263,8 @@
             removed_api_file: ":non-updatable-module-lib-removed.txt",
         },
         last_released: {
-            api_file: ":android-non-updatable.api.module-lib.latest",
-            removed_api_file: ":android-non-updatable-removed.api.module-lib.latest",
+            api_file: ":android-non-updatable.api.combined.module-lib.latest",
+            removed_api_file: ":android-non-updatable-removed.api.combined.module-lib.latest",
             baseline_file: ":android-non-updatable-incompatibilities.api.module-lib.latest",
         },
         api_lint: {
diff --git a/core/api/current.txt b/core/api/current.txt
index 16de8fe7..6c5a5c9 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -26611,7 +26611,7 @@
 
   public abstract static class MediaController.Callback {
     ctor public MediaController.Callback();
-    method public void onAudioInfoChanged(android.media.session.MediaController.PlaybackInfo);
+    method public void onAudioInfoChanged(@NonNull android.media.session.MediaController.PlaybackInfo);
     method public void onExtrasChanged(@Nullable android.os.Bundle);
     method public void onMetadataChanged(@Nullable android.media.MediaMetadata);
     method public void onPlaybackStateChanged(@Nullable android.media.session.PlaybackState);
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 9ea55f5..c6a1546 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -103,7 +103,8 @@
     @IntDef(prefix = {"MODE_BACKGROUND_ACTIVITY_START_"}, value = {
             MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED,
             MODE_BACKGROUND_ACTIVITY_START_ALLOWED,
-            MODE_BACKGROUND_ACTIVITY_START_DENIED})
+            MODE_BACKGROUND_ACTIVITY_START_DENIED,
+            MODE_BACKGROUND_ACTIVITY_START_COMPAT})
     public @interface BackgroundActivityStartMode {}
     /**
      * No explicit value chosen. The system will decide whether to grant privileges.
@@ -117,6 +118,13 @@
      * Deny the {@link PendingIntent} to use the background activity start privileges.
      */
     public static final int MODE_BACKGROUND_ACTIVITY_START_DENIED = 2;
+    /**
+     * Special behavior for compatibility.
+     * Similar to {@link #MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED}
+     *
+     * @hide
+     */
+    public static final int MODE_BACKGROUND_ACTIVITY_START_COMPAT = -1;
 
     /**
      * The package name that created the options.
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index d4812dd..76c1ed6 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -18,6 +18,7 @@
 
 import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
 import static android.app.ConfigurationController.createNewConfigAndUpdateIfNotNull;
+import static android.app.Flags.skipBgMemTrimOnFgApp;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.app.servertransaction.ActivityLifecycleItem.ON_CREATE;
@@ -7078,6 +7079,11 @@
         if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Trimming memory to level: " + level);
 
         try {
+            if (skipBgMemTrimOnFgApp()
+                    && mLastProcessState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
+                    && level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
+                return;
+            }
             if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) {
                 PropertyInvalidatedCache.onTrimMemory();
             }
diff --git a/core/java/android/app/ComponentOptions.java b/core/java/android/app/ComponentOptions.java
index 397477d..0e8e2e3 100644
--- a/core/java/android/app/ComponentOptions.java
+++ b/core/java/android/app/ComponentOptions.java
@@ -18,6 +18,7 @@
 
 import static android.app.ActivityOptions.BackgroundActivityStartMode;
 import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_COMPAT;
 import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
 import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
 
@@ -54,7 +55,7 @@
     public static final String KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION =
             "android.pendingIntent.backgroundActivityAllowedByPermission";
 
-    private @Nullable Boolean mPendingIntentBalAllowed = null;
+    private Integer mPendingIntentBalAllowed = MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
     private boolean mPendingIntentBalAllowedByPermission = false;
 
     ComponentOptions() {
@@ -65,12 +66,9 @@
         // results they want, which is their loss.
         opts.setDefusable(true);
 
-        boolean pendingIntentBalAllowedIsSetExplicitly =
-                opts.containsKey(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED);
-        if (pendingIntentBalAllowedIsSetExplicitly) {
-            mPendingIntentBalAllowed =
-                    opts.getBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED);
-        }
+        mPendingIntentBalAllowed =
+                opts.getInt(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED,
+                        MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED);
         setPendingIntentBackgroundActivityLaunchAllowedByPermission(
                 opts.getBoolean(
                         KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, false));
@@ -85,7 +83,8 @@
      * @hide
      */
     @Deprecated public void setPendingIntentBackgroundActivityLaunchAllowed(boolean allowed) {
-        mPendingIntentBalAllowed = allowed;
+        mPendingIntentBalAllowed = allowed ? MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+                : MODE_BACKGROUND_ACTIVITY_START_DENIED;
     }
 
     /**
@@ -98,11 +97,8 @@
      * @hide
      */
     @Deprecated public boolean isPendingIntentBackgroundActivityLaunchAllowed() {
-        if (mPendingIntentBalAllowed == null) {
-            // cannot return null, so return the value used up to API level 33 for compatibility
-            return true;
-        }
-        return mPendingIntentBalAllowed;
+        // cannot return all detail, so return the value used up to API level 33 for compatibility
+        return mPendingIntentBalAllowed != MODE_BACKGROUND_ACTIVITY_START_DENIED;
     }
 
     /**
@@ -119,16 +115,15 @@
             @BackgroundActivityStartMode int state) {
         switch (state) {
             case MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED:
-                mPendingIntentBalAllowed = null;
-                break;
-            case MODE_BACKGROUND_ACTIVITY_START_ALLOWED:
-                mPendingIntentBalAllowed = true;
-                break;
             case MODE_BACKGROUND_ACTIVITY_START_DENIED:
-                mPendingIntentBalAllowed = false;
+            case MODE_BACKGROUND_ACTIVITY_START_COMPAT:
+            case MODE_BACKGROUND_ACTIVITY_START_ALLOWED:
+                mPendingIntentBalAllowed = state;
                 break;
             default:
-                throw new IllegalArgumentException(state + " is not valid");
+                // Assume that future values are some variant of allowing the start.
+                mPendingIntentBalAllowed = MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+                break;
         }
         return this;
     }
@@ -141,13 +136,7 @@
      * @see #setPendingIntentBackgroundActivityStartMode(int)
      */
     public @BackgroundActivityStartMode int getPendingIntentBackgroundActivityStartMode() {
-        if (mPendingIntentBalAllowed == null) {
-            return MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
-        } else if (mPendingIntentBalAllowed) {
-            return MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
-        } else {
-            return MODE_BACKGROUND_ACTIVITY_START_DENIED;
-        }
+        return mPendingIntentBalAllowed;
     }
 
     /**
@@ -170,8 +159,8 @@
     /** @hide */
     public Bundle toBundle() {
         Bundle b = new Bundle();
-        if (mPendingIntentBalAllowed != null) {
-            b.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, mPendingIntentBalAllowed);
+        if (mPendingIntentBalAllowed != MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) {
+            b.putInt(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, mPendingIntentBalAllowed);
         }
         if (mPendingIntentBalAllowedByPermission) {
             b.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION,
diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig
index bb24fd1..fa646a7 100644
--- a/core/java/android/app/activity_manager.aconfig
+++ b/core/java/android/app/activity_manager.aconfig
@@ -70,3 +70,14 @@
          purpose: PURPOSE_BUGFIX
      }
 }
+
+flag {
+     namespace: "backstage_power"
+     name: "skip_bg_mem_trim_on_fg_app"
+     description: "Skip background memory trim event on foreground processes."
+     is_fixed_read_only: true
+     bug: "308927629"
+     metadata {
+         purpose: PURPOSE_BUGFIX
+     }
+}
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 4154e66..ad1ae98 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -115,6 +115,16 @@
 }
 
 flag {
+  name: "hsum_unlock_notification_fix"
+  namespace: "enterprise"
+  description: "Using the right userId when starting the work profile unlock flow "
+  bug: "327350831"
+  metadata {
+      purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
   name: "dumpsys_policy_engine_migration_enabled"
   namespace: "enterprise"
   description: "Update DumpSys to include information about migrated APIs in DPE"
@@ -318,6 +328,13 @@
 }
 
 flag {
+    name: "backup_connected_apps_settings"
+    namespace: "enterprise"
+    description: "backup and restore connected work and personal apps user settings across devices"
+    bug: "175067666"
+}
+
+flag {
     name: "headless_single_user_compatibility_fix"
     namespace: "enterprise"
     description: "Fix for compatibility issue introduced from using single_user mode on pre-Android V builds"
@@ -336,3 +353,13 @@
       purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+  name: "onboarding_consentless_bugreports"
+  namespace: "enterprise"
+  description: "Allow subsequent bugreports to skip user consent within a time frame"
+  bug: "340439309"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/core/java/android/content/IntentSender.java b/core/java/android/content/IntentSender.java
index b074e8b..2e252c1 100644
--- a/core/java/android/content/IntentSender.java
+++ b/core/java/android/content/IntentSender.java
@@ -60,6 +60,10 @@
  * {@link android.app.PendingIntent#getIntentSender() PendingIntent.getIntentSender()}.
  */
 public class IntentSender implements Parcelable {
+    private static final Bundle SEND_INTENT_DEFAULT_OPTIONS =
+            ActivityOptions.makeBasic().setPendingIntentBackgroundActivityStartMode(
+                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_COMPAT).toBundle();
+
     @UnsupportedAppUsage
     private final IIntentSender mTarget;
     IBinder mWhitelistToken;
@@ -161,7 +165,8 @@
      */
     public void sendIntent(Context context, int code, Intent intent,
             OnFinished onFinished, Handler handler) throws SendIntentException {
-        sendIntent(context, code, intent, onFinished, handler, null, null /* options */);
+        sendIntent(context, code, intent, onFinished, handler, null,
+                SEND_INTENT_DEFAULT_OPTIONS);
     }
 
     /**
@@ -194,7 +199,7 @@
             OnFinished onFinished, Handler handler, String requiredPermission)
             throws SendIntentException {
         sendIntent(context, code, intent, onFinished, handler, requiredPermission,
-                null /* options */);
+                SEND_INTENT_DEFAULT_OPTIONS);
     }
 
     /**
diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java
index 8220313..57ee622 100644
--- a/core/java/android/content/pm/CrossProfileApps.java
+++ b/core/java/android/content/pm/CrossProfileApps.java
@@ -326,6 +326,7 @@
      * @return whether the specified user is a profile.
      */
     @FlaggedApi(FLAG_ALLOW_QUERYING_PROFILE_TYPE)
+    @SuppressWarnings("UserHandleName")
     public boolean isProfile(@NonNull UserHandle userHandle) {
         // Note that this is not a security check, but rather a check for correct use.
         // The actual security check is performed by UserManager.
@@ -343,6 +344,7 @@
      * @return whether the specified user is a managed profile.
      */
     @FlaggedApi(FLAG_ALLOW_QUERYING_PROFILE_TYPE)
+    @SuppressWarnings("UserHandleName")
     public boolean isManagedProfile(@NonNull UserHandle userHandle) {
         // Note that this is not a security check, but rather a check for correct use.
         // The actual security check is performed by UserManager.
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 885f4c5..982224b 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -806,7 +806,7 @@
      *
      * <aside class="note"><b>Note:</b> If the app targets
      * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}
-     * or after, The width measurement reflects the window size without excluding insets.
+     * or after, the width measurement reflects the window size without excluding insets.
      * Otherwise, the measurement excludes window insets even when the app is displayed edge to edge
      * using {@link android.view.Window#setDecorFitsSystemWindows(boolean)
      * Window#setDecorFitsSystemWindows(boolean)}.</aside>
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index e2b409f..7f3c49d 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -1571,8 +1571,7 @@
         }
 
         // Allow RAW formats, even when not advertised.
-        if (inputFormat == ImageFormat.RAW_PRIVATE || inputFormat == ImageFormat.RAW10
-                || inputFormat == ImageFormat.RAW12 || inputFormat == ImageFormat.RAW_SENSOR) {
+        if (isRawFormat(inputFormat)) {
             return true;
         }
 
@@ -1642,6 +1641,11 @@
                 }
             }
 
+            // Allow RAW formats, even when not advertised.
+            if (Flags.multiResRawReprocessing() && isRawFormat(inputFormat)) {
+                return;
+            }
+
             if (validFormat == false) {
                 throw new IllegalArgumentException("multi-resolution input format " +
                         inputFormat + " is not valid");
@@ -2584,6 +2588,11 @@
         return mCharacteristics;
     }
 
+    private boolean isRawFormat(int format) {
+        return (format == ImageFormat.RAW_PRIVATE || format == ImageFormat.RAW10
+                || format == ImageFormat.RAW12 || format == ImageFormat.RAW_SENSOR);
+    }
+
     /**
      * Listener for binder death.
      *
diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java
index 960e84d..a818df5 100644
--- a/core/java/android/os/BugreportManager.java
+++ b/core/java/android/os/BugreportManager.java
@@ -252,7 +252,8 @@
                     params.getMode(),
                     params.getFlags(),
                     dsListener,
-                    isScreenshotRequested);
+                    isScreenshotRequested,
+                    /* skipUserConsent = */ false);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         } catch (FileNotFoundException e) {
@@ -313,6 +314,7 @@
                     bugreportFd.getFileDescriptor(),
                     bugreportFile,
                     /* keepBugreportOnRetrieval = */ false,
+                    /* skipUserConsent = */ false,
                     dsListener);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 2f0d634..80d3566 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -368,17 +368,18 @@
     public static final String DISALLOW_WIFI_TETHERING = "no_wifi_tethering";
 
     /**
-     * Specifies if a user is disallowed from being granted admin privileges.
+     * Restricts a user's ability to possess or grant admin privileges.
      *
-     * <p>This restriction limits ability of other admin users to grant admin
-     * privileges to selected user.
+     * <p>When set to <code>true</code>, this prevents the user from:
+     *     <ul>
+     *         <li>Becoming an admin</li>
+     *         <li>Giving other users admin privileges</li>
+     *     </ul>
      *
-     * <p>This restriction has no effect in a mode that does not allow multiple admins.
+     * <p>This restriction is only effective in environments where multiple admins are allowed.
      *
-     * <p>The default value is <code>false</code>.
+     * <p>Key for user restrictions. Type: Boolean. Default: <code>false</code>.
      *
-     * <p>Key for user restrictions.
-     * <p>Type: Boolean
      * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
      * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
      * @see #getUserRestrictions()
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 4f5b67c..3738c26 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -17032,6 +17032,28 @@
          */
         public static final String ENABLE_BACK_ANIMATION = "enable_back_animation";
 
+        /**
+         * An allow list of packages for which the user has granted the permission to communicate
+         * across profiles.
+         *
+         * @hide
+         */
+        @Readable
+        @FlaggedApi(android.app.admin.flags.Flags.FLAG_BACKUP_CONNECTED_APPS_SETTINGS)
+        public static final String CONNECTED_APPS_ALLOWED_PACKAGES =
+                "connected_apps_allowed_packages";
+
+        /**
+         * A block list of packages for which the user has denied the permission to communicate
+         * across profiles.
+         *
+         * @hide
+         */
+        @Readable
+        @FlaggedApi(android.app.admin.flags.Flags.FLAG_BACKUP_CONNECTED_APPS_SETTINGS)
+        public static final String CONNECTED_APPS_DISALLOWED_PACKAGES =
+                "connected_apps_disallowed_packages";
+
         /** @hide */ public static String zenModeToString(int mode) {
             if (mode == ZEN_MODE_IMPORTANT_INTERRUPTIONS) return "ZEN_MODE_IMPORTANT_INTERRUPTIONS";
             if (mode == ZEN_MODE_ALARMS) return "ZEN_MODE_ALARMS";
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 38ab590..71066ac 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -433,7 +433,8 @@
                         mTrackingConfirmKey = event.getKeyCode();
                     }
                     case KeyEvent.ACTION_UP -> {
-                        if (mTrackingConfirmKey != event.getKeyCode()) {
+                        if (mTrackingConfirmKey == null
+                                || mTrackingConfirmKey != event.getKeyCode()) {
                             return true;
                         }
 
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index cce4f7b..a78a417 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -310,6 +310,7 @@
          * @see Layout#getUseBoundsForWidth()
          * @see Layout.Builder#setUseBoundsForWidth(boolean)
          */
+        @SuppressLint("MissingGetterMatchingBuilder")  // The base class `Layout` has a getter.
         @NonNull
         @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
         public Builder setUseBoundsForWidth(boolean useBoundsForWidth) {
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 3dd3a9e..95460a3 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -447,6 +447,7 @@
          * @see Layout#getUseBoundsForWidth()
          * @see Layout.Builder#setUseBoundsForWidth(boolean)
          */
+        @SuppressLint("MissingGetterMatchingBuilder")  // The base class `Layout` has a getter.
         @NonNull
         @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
         public Builder setUseBoundsForWidth(boolean useBoundsForWidth) {
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 4475418..6464239 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -916,6 +916,12 @@
      *         {@code getWindowManager()} or {@code getSystemService(Context.WINDOW_SERVICE)}), the
      *         size of the current app window is returned. As a result, in multi-window mode, the
      *         returned size can be smaller than the size of the device screen.
+     *         The returned window size can vary depending on API level:
+     *         <ul>
+     *             <li>API level 35 and above, the window size will be returned.
+     *             <li>API level 34 and below, the window size minus system decoration areas and
+     *             display cutout is returned.
+     *         </ul>
      *     <li>If size is requested from a non-activity context (for example, the application
      *         context, where the WindowManager is accessed by
      *         {@code getApplicationContext().getSystemService(Context.WINDOW_SERVICE)}), the
@@ -924,9 +930,10 @@
      *             <li>API level 29 and below &mdash; The size of the entire display (based on
      *                 current rotation) minus system decoration areas is returned.
      *             <li>API level 30 and above &mdash; The size of the top running activity in the
-     *                 current process is returned. If the current process has no running
-     *                 activities, the size of the device default display, including system
-     *                 decoration areas, is returned.
+     *                 current process is returned, system decoration areas exclusion follows the
+     *                 behavior defined above, based on the caller's API level. If the current
+     *                 process has no running activities, the size of the device default display,
+     *                 including system decoration areas, is returned.
      *         </ul>
      * </ul>
      *
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index d7f2b01..fd10a1f 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -303,7 +303,7 @@
     }
 
     /** Not running an animation. */
-    @VisibleForTesting
+    @VisibleForTesting(visibility = PACKAGE)
     public static final int ANIMATION_TYPE_NONE = -1;
 
     /** Running animation will show insets */
@@ -317,7 +317,7 @@
     public static final int ANIMATION_TYPE_USER = 2;
 
     /** Running animation will resize insets */
-    @VisibleForTesting
+    @VisibleForTesting(visibility = PACKAGE)
     public static final int ANIMATION_TYPE_RESIZE = 3;
 
     @Retention(RetentionPolicy.SOURCE)
@@ -1714,7 +1714,7 @@
         mImeSourceConsumer.onWindowFocusLost();
     }
 
-    @VisibleForTesting
+    @VisibleForTesting(visibility = PACKAGE)
     public @AnimationType int getAnimationType(@InsetsType int type) {
         for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
             InsetsAnimationControlRunner control = mRunningAnimations.get(i).runner;
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index fdb2a6e..6c670f5 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -17,6 +17,7 @@
 package android.view;
 
 import static android.view.InsetsController.ANIMATION_TYPE_NONE;
+import static android.view.InsetsController.ANIMATION_TYPE_RESIZE;
 import static android.view.InsetsController.AnimationType;
 import static android.view.InsetsController.DEBUG;
 import static android.view.InsetsSourceConsumerProto.ANIMATION_STATE;
@@ -31,6 +32,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.Nullable;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.util.Log;
 import android.util.proto.ProtoOutputStream;
@@ -179,10 +181,11 @@
                     mController.notifyVisibilityChanged();
                 }
 
-                // If we have a new leash, make sure visibility is up-to-date, even though we
-                // didn't want to run an animation above.
-                if (mController.getAnimationType(mType) == ANIMATION_TYPE_NONE) {
-                    applyRequestedVisibilityToControl();
+                // If there is no animation controlling the leash, make sure the visibility and the
+                // position is up-to-date.
+                final int animType = mController.getAnimationType(mType);
+                if (animType == ANIMATION_TYPE_NONE || animType == ANIMATION_TYPE_RESIZE) {
+                    applyRequestedVisibilityAndPositionToControl();
                 }
 
                 // Remove the surface that owned by last control when it lost.
@@ -371,21 +374,27 @@
         if (DEBUG) Log.d(TAG, "updateSource: " + newSource);
     }
 
-    private void applyRequestedVisibilityToControl() {
-        if (mSourceControl == null || mSourceControl.getLeash() == null) {
+    private void applyRequestedVisibilityAndPositionToControl() {
+        if (mSourceControl == null) {
+            return;
+        }
+        final SurfaceControl leash = mSourceControl.getLeash();
+        if (leash == null) {
             return;
         }
 
         final boolean requestedVisible = (mController.getRequestedVisibleTypes() & mType) != 0;
+        final Point surfacePosition = mSourceControl.getSurfacePosition();
         try (Transaction t = mTransactionSupplier.get()) {
             if (DEBUG) Log.d(TAG, "applyRequestedVisibilityToControl: " + requestedVisible);
             if (requestedVisible) {
-                t.show(mSourceControl.getLeash());
+                t.show(leash);
             } else {
-                t.hide(mSourceControl.getLeash());
+                t.hide(leash);
             }
             // Ensure the alpha value is aligned with the actual requested visibility.
-            t.setAlpha(mSourceControl.getLeash(), requestedVisible ? 1 : 0);
+            t.setAlpha(leash, requestedVisible ? 1 : 0);
+            t.setPosition(leash, surfacePosition.x, surfacePosition.y);
             t.apply();
         }
         onPerceptible(requestedVisible);
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index f22e8f5..0f54940b 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -781,7 +781,7 @@
      * <p>
      * The metrics describe the size of the area the window would occupy with
      * {@link LayoutParams#MATCH_PARENT MATCH_PARENT} width and height, and the {@link WindowInsets}
-     * such a window would have.
+     * such a window would have. The {@link WindowInsets} are not deducted from the bounds.
      * <p>
      * The value of this is based on the <b>current</b> windowing state of the system.
      *
@@ -811,7 +811,7 @@
      * <p>
      * The metrics describe the size of the largest potential area the window might occupy with
      * {@link LayoutParams#MATCH_PARENT MATCH_PARENT} width and height, and the {@link WindowInsets}
-     * such a window would have.
+     * such a window would have. The {@link WindowInsets} are not deducted from the bounds.
      * <p>
      * Note that this might still be smaller than the size of the physical display if certain areas
      * of the display are not available to windows created in this {@link Context}.
@@ -4264,11 +4264,9 @@
          *         no letterbox is applied."/>
          *
          * <p>
-         * A cutout in the corner is considered to be on the short edge: <br/>
-         * <img src="{@docRoot}reference/android/images/display_cutout/short_edge/fullscreen_corner_no_letterbox.png"
-         * height="720"
-         * alt="Screenshot of a fullscreen activity on a display with a cutout in the corner in
-         *         portrait, no letterbox is applied."/>
+         * A cutout in the corner can be considered to be on different edge in different device
+         * rotations. This behavior may vary from device to device. Use this flag is possible to
+         * letterbox your app if the display cutout is at corner.
          *
          * <p>
          * On the other hand, should the cutout be on the long edge of the display, a letterbox will
diff --git a/core/java/android/view/WindowMetrics.java b/core/java/android/view/WindowMetrics.java
index 26298bc..8bcc9de 100644
--- a/core/java/android/view/WindowMetrics.java
+++ b/core/java/android/view/WindowMetrics.java
@@ -101,9 +101,13 @@
      * Returns the bounds of the area associated with this window or {@code UiContext}.
      * <p>
      * <b>Note that the size of the reported bounds can have different size than
-     * {@link Display#getSize(Point)}.</b> This method reports the window size including all system
-     * bar areas, while {@link Display#getSize(Point)} reports the area excluding navigation bars
-     * and display cutout areas. The value reported by {@link Display#getSize(Point)} can be
+     * {@link Display#getSize(Point)} based on your target API level and calling context.</b>
+     * This method reports the window size including all system
+     * bar areas, while {@link Display#getSize(Point)} can report the area excluding navigation bars
+     * and display cutout areas depending on the calling context and target SDK level. Please refer
+     * to {@link Display#getSize(Point)} for details.
+     * <p>
+     * The value reported by {@link Display#getSize(Point)} excluding system decoration areas can be
      * obtained by using:
      * <pre class="prettyprint">
      * final WindowMetrics metrics = windowManager.getCurrentWindowMetrics();
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index da2bf9d..4de3a7b 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -124,6 +124,7 @@
     namespace: "accessibility"
     name: "add_type_window_control"
     is_exported: true
+    is_fixed_read_only: true
     description: "adds new TYPE_WINDOW_CONTROL to AccessibilityWindowInfo for detecting Window Decorations"
     bug: "320445550"
 }
diff --git a/core/java/android/window/BackProgressAnimator.java b/core/java/android/window/BackProgressAnimator.java
index 163e43a..6fe0784 100644
--- a/core/java/android/window/BackProgressAnimator.java
+++ b/core/java/android/window/BackProgressAnimator.java
@@ -114,7 +114,6 @@
      *                 dispatches as the progress animation updates.
      */
     public void onBackStarted(BackMotionEvent event, ProgressCallback callback) {
-        reset();
         mLastBackEvent = event;
         mCallback = callback;
         mBackAnimationInProgress = true;
diff --git a/core/java/android/window/RemoteTransition.java b/core/java/android/window/RemoteTransition.java
index 4cc7ec5..15b3c44 100644
--- a/core/java/android/window/RemoteTransition.java
+++ b/core/java/android/window/RemoteTransition.java
@@ -22,15 +22,12 @@
 import android.os.IBinder;
 import android.os.Parcelable;
 
-import com.android.internal.util.DataClass;
-
 /**
  * Represents a remote transition animation and information required to run it (eg. the app thread
  * that needs to be boosted).
  * @hide
  */
-@DataClass(genToString = true, genSetters = true, genAidl = true)
-public class RemoteTransition implements Parcelable {
+public final class RemoteTransition implements Parcelable {
 
     /** The actual remote-transition interface used to run the transition animation. */
     private @NonNull IRemoteTransition mRemoteTransition;
@@ -41,12 +38,18 @@
     /** A name for this that can be used for debugging. */
     private @Nullable String mDebugName;
 
-    /** Constructs with no app thread (animation runs in shell). */
+    /**
+     * Constructs with no app thread (animation runs in shell).
+     * @hide
+     */
     public RemoteTransition(@NonNull IRemoteTransition remoteTransition) {
         this(remoteTransition, null /* appThread */, null /* debugName */);
     }
 
-    /** Constructs with no app thread (animation runs in shell). */
+    /**
+     * Constructs with no app thread (animation runs in shell).
+     * @hide
+     */
     public RemoteTransition(@NonNull IRemoteTransition remoteTransition,
             @Nullable String debugName) {
         this(remoteTransition, null /* appThread */, debugName);
@@ -57,21 +60,6 @@
         return mRemoteTransition.asBinder();
     }
 
-
-
-    // Code below generated by codegen v1.0.23.
-    //
-    // DO NOT MODIFY!
-    // CHECKSTYLE:OFF Generated code
-    //
-    // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/window/RemoteTransition.java
-    //
-    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
-    //   Settings > Editor > Code Style > Formatter Control
-    //@formatter:off
-
-
     /**
      * Creates a new RemoteTransition.
      *
@@ -81,8 +69,8 @@
      *   The application thread that will be running the remote transition.
      * @param debugName
      *   A name for this that can be used for debugging.
+     * @hide
      */
-    @DataClass.Generated.Member
     public RemoteTransition(
             @NonNull IRemoteTransition remoteTransition,
             @Nullable IApplicationThread appThread,
@@ -98,16 +86,16 @@
 
     /**
      * The actual remote-transition interface used to run the transition animation.
+     * @hide
      */
-    @DataClass.Generated.Member
     public @NonNull IRemoteTransition getRemoteTransition() {
         return mRemoteTransition;
     }
 
     /**
      * The application thread that will be running the remote transition.
+     * @hide
      */
-    @DataClass.Generated.Member
     public @Nullable IApplicationThread getAppThread() {
         return mAppThread;
     }
@@ -115,15 +103,14 @@
     /**
      * A name for this that can be used for debugging.
      */
-    @DataClass.Generated.Member
     public @Nullable String getDebugName() {
         return mDebugName;
     }
 
     /**
      * The actual remote-transition interface used to run the transition animation.
+     * @hide
      */
-    @DataClass.Generated.Member
     public @NonNull RemoteTransition setRemoteTransition(@NonNull IRemoteTransition value) {
         mRemoteTransition = value;
         com.android.internal.util.AnnotationValidations.validate(
@@ -133,8 +120,8 @@
 
     /**
      * The application thread that will be running the remote transition.
+     * @hide
      */
-    @DataClass.Generated.Member
     public @NonNull RemoteTransition setAppThread(@NonNull IApplicationThread value) {
         mAppThread = value;
         return this;
@@ -143,14 +130,12 @@
     /**
      * A name for this that can be used for debugging.
      */
-    @DataClass.Generated.Member
     public @NonNull RemoteTransition setDebugName(@NonNull String value) {
         mDebugName = value;
         return this;
     }
 
     @Override
-    @DataClass.Generated.Member
     public String toString() {
         // You can override field toString logic by defining methods like:
         // String fieldNameToString() { ... }
@@ -163,7 +148,6 @@
     }
 
     @Override
-    @DataClass.Generated.Member
     public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
         // You can override field parcelling by defining methods like:
         // void parcelFieldName(Parcel dest, int flags) { ... }
@@ -178,12 +162,10 @@
     }
 
     @Override
-    @DataClass.Generated.Member
     public int describeContents() { return 0; }
 
     /** @hide */
     @SuppressWarnings({"unchecked", "RedundantCast"})
-    @DataClass.Generated.Member
     protected RemoteTransition(@NonNull android.os.Parcel in) {
         // You can override field unparcelling by defining methods like:
         // static FieldType unparcelFieldName(Parcel in) { ... }
@@ -198,11 +180,8 @@
                 NonNull.class, null, mRemoteTransition);
         this.mAppThread = appThread;
         this.mDebugName = debugName;
-
-        // onConstructed(); // You can define this method to get a callback
     }
 
-    @DataClass.Generated.Member
     public static final @NonNull Parcelable.Creator<RemoteTransition> CREATOR
             = new Parcelable.Creator<RemoteTransition>() {
         @Override
@@ -215,17 +194,4 @@
             return new RemoteTransition(in);
         }
     };
-
-    @DataClass.Generated(
-            time = 1678926409863L,
-            codegenVersion = "1.0.23",
-            sourceFile = "frameworks/base/core/java/android/window/RemoteTransition.java",
-            inputSignatures = "private @android.annotation.NonNull android.window.IRemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.app.IApplicationThread mAppThread\nprivate @android.annotation.Nullable java.lang.String mDebugName\npublic @android.annotation.Nullable android.os.IBinder asBinder()\nclass RemoteTransition extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)")
-    @Deprecated
-    private void __metadata() {}
-
-
-    //@formatter:on
-    // End of generated code
-
 }
diff --git a/core/java/android/window/TaskSnapshot.java b/core/java/android/window/TaskSnapshot.java
index a2e3d40..f0144cb 100644
--- a/core/java/android/window/TaskSnapshot.java
+++ b/core/java/android/window/TaskSnapshot.java
@@ -33,6 +33,8 @@
 import android.view.Surface;
 import android.view.WindowInsetsController;
 
+import com.android.window.flags.Flags;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
@@ -334,7 +336,8 @@
      */
     public synchronized void removeReference(@ReferenceFlags int usage) {
         mInternalReferences &= ~usage;
-        if (mInternalReferences == 0 && mSnapshot != null && !mSnapshot.isClosed()) {
+        if (Flags.releaseSnapshotAggressively() && mInternalReferences == 0 && mSnapshot != null
+                && !mSnapshot.isClosed()) {
             mSnapshot.close();
         }
     }
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index bcae571..4ffd880 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -54,6 +54,8 @@
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
@@ -69,6 +71,7 @@
      * Modes are only a sub-set of all the transit-types since they are per-container
      * @hide
      */
+    @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = { "TRANSIT_" }, value = {
             TRANSIT_NONE,
             TRANSIT_OPEN,
@@ -102,11 +105,11 @@
     /** The container is the display. */
     public static final int FLAG_IS_DISPLAY = 1 << 5;
 
+    // TODO(b/194540864): Once we can include all windows in transition, then replace this with
+    // something like FLAG_IS_SYSTEM_ALERT instead. Then we can do mixed rotations.
     /**
      * Only for IS_DISPLAY containers. Is set if the display has system alert windows. This is
      * used to prevent seamless rotation.
-     * TODO(b/194540864): Once we can include all windows in transition, then replace this with
-     *         something like FLAG_IS_SYSTEM_ALERT instead. Then we can do mixed rotations.
      */
     public static final int FLAG_DISPLAY_HAS_ALERT_WINDOWS = 1 << 7;
 
@@ -173,6 +176,7 @@
     public static final int FLAGS_IS_OCCLUDED_NO_ANIMATION = FLAG_IS_OCCLUDED | FLAG_NO_ANIMATION;
 
     /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = { "FLAG_" }, value = {
             FLAG_NONE,
             FLAG_SHOW_WALLPAPER,
@@ -267,11 +271,11 @@
     }
 
     /** @see #getRoot */
-    public void addRoot(Root other) {
+    public void addRoot(@NonNull Root other) {
         mRoots.add(other);
     }
 
-    public void setAnimationOptions(AnimationOptions options) {
+    public void setAnimationOptions(@Nullable AnimationOptions options) {
         mOptions = options;
     }
 
@@ -336,6 +340,7 @@
         return mRoots.get(0).mLeash;
     }
 
+    @Nullable
     public AnimationOptions getAnimationOptions() {
         return mOptions;
     }
@@ -601,7 +606,7 @@
      * Updates the callsites of all the surfaces in this transition, which aids in the debugging of
      * lingering surfaces.
      */
-    public void setUnreleasedWarningCallSiteForAllSurfaces(String callsite) {
+    public void setUnreleasedWarningCallSiteForAllSurfaces(@Nullable String callsite) {
         for (int i = mChanges.size() - 1; i >= 0; --i) {
             mChanges.get(i).getLeash().setUnreleasedWarningCallSite(callsite);
         }
@@ -613,6 +618,7 @@
      * the caller's references. Use this only if you need to "send" this to a local function which
      * assumes it is being called from a remote caller.
      */
+    @NonNull
     public TransitionInfo localRemoteCopy() {
         final TransitionInfo out = new TransitionInfo(mType, mFlags);
         out.mTrack = mTrack;
@@ -891,7 +897,7 @@
             return mTaskInfo;
         }
 
-        public boolean getAllowEnterPip() {
+        public boolean isAllowEnterPip() {
             return mAllowEnterPip;
         }
 
@@ -1042,6 +1048,7 @@
     }
 
     /** Represents animation options during a transition */
+    @SuppressWarnings("UserHandleName")
     public static final class AnimationOptions implements Parcelable {
 
         private int mType;
@@ -1061,7 +1068,7 @@
             mType = type;
         }
 
-        public AnimationOptions(Parcel in) {
+        private AnimationOptions(Parcel in) {
             mType = in.readInt();
             mEnterResId = in.readInt();
             mExitResId = in.readInt();
@@ -1076,14 +1083,17 @@
         }
 
         /** Make basic customized animation for a package */
-        public static AnimationOptions makeCommonAnimOptions(String packageName) {
+        @NonNull
+        public static AnimationOptions makeCommonAnimOptions(@NonNull String packageName) {
             AnimationOptions options = new AnimationOptions(ANIM_FROM_STYLE);
             options.mPackageName = packageName;
             return options;
         }
 
+        /** Make custom animation from the content of LayoutParams */
+        @NonNull
         public static AnimationOptions makeAnimOptionsFromLayoutParameters(
-                WindowManager.LayoutParams lp) {
+                @NonNull WindowManager.LayoutParams lp) {
             AnimationOptions options = new AnimationOptions(ANIM_FROM_STYLE);
             options.mPackageName = lp.packageName;
             options.mAnimations = lp.windowAnimations;
@@ -1091,7 +1101,7 @@
         }
 
         /** Add customized window animations */
-        public void addOptionsFromLayoutParameters(WindowManager.LayoutParams lp) {
+        public void addOptionsFromLayoutParameters(@NonNull WindowManager.LayoutParams lp) {
             mAnimations = lp.windowAnimations;
         }
 
@@ -1111,8 +1121,11 @@
             customTransition.addCustomActivityTransition(enterResId, exitResId, backgroundColor);
         }
 
-        public static AnimationOptions makeCustomAnimOptions(String packageName, int enterResId,
-                int exitResId, @ColorInt int backgroundColor, boolean overrideTaskTransition) {
+        /** Make options for a custom animation based on anim resources */
+        @NonNull
+        public static AnimationOptions makeCustomAnimOptions(@NonNull String packageName,
+                int enterResId, int exitResId, @ColorInt int backgroundColor,
+                boolean overrideTaskTransition) {
             AnimationOptions options = new AnimationOptions(ANIM_CUSTOM);
             options.mPackageName = packageName;
             options.mEnterResId = enterResId;
@@ -1122,6 +1135,8 @@
             return options;
         }
 
+        /** Make options for a clip-reveal animation. */
+        @NonNull
         public static AnimationOptions makeClipRevealAnimOptions(int startX, int startY, int width,
                 int height) {
             AnimationOptions options = new AnimationOptions(ANIM_CLIP_REVEAL);
@@ -1129,6 +1144,8 @@
             return options;
         }
 
+        /** Make options for a scale-up animation. */
+        @NonNull
         public static AnimationOptions makeScaleUpAnimOptions(int startX, int startY, int width,
                 int height) {
             AnimationOptions options = new AnimationOptions(ANIM_SCALE_UP);
@@ -1136,7 +1153,9 @@
             return options;
         }
 
-        public static AnimationOptions makeThumbnailAnimOptions(HardwareBuffer srcThumb,
+        /** Make options for a thumbnail-scaling animation. */
+        @NonNull
+        public static AnimationOptions makeThumbnailAnimOptions(@NonNull HardwareBuffer srcThumb,
                 int startX, int startY, boolean scaleUp) {
             AnimationOptions options = new AnimationOptions(
                     scaleUp ? ANIM_THUMBNAIL_SCALE_UP : ANIM_THUMBNAIL_SCALE_DOWN);
@@ -1145,11 +1164,15 @@
             return options;
         }
 
+        /** Make options for an animation that spans activities of different profiles. */
+        @NonNull
         public static AnimationOptions makeCrossProfileAnimOptions() {
             AnimationOptions options = new AnimationOptions(ANIM_OPEN_CROSS_PROFILE_APPS);
             return options;
         }
 
+        /** Make options designating this as a scene-transition animation. */
+        @NonNull
         public static AnimationOptions makeSceneTransitionAnimOptions() {
             AnimationOptions options = new AnimationOptions(ANIM_SCENE_TRANSITION);
             return options;
@@ -1175,14 +1198,17 @@
             return mOverrideTaskTransition;
         }
 
+        @Nullable
         public String getPackageName() {
             return mPackageName;
         }
 
+        @NonNull
         public Rect getTransitionBounds() {
             return mTransitionBounds;
         }
 
+        @Nullable
         public HardwareBuffer getThumbnail() {
             return mThumbnail;
         }
@@ -1192,12 +1218,13 @@
         }
 
         /** Return customized activity transition if existed. */
+        @Nullable
         public CustomActivityTransition getCustomActivityTransition(boolean open) {
             return open ? mCustomActivityOpenTransition : mCustomActivityCloseTransition;
         }
 
         @Override
-        public void writeToParcel(Parcel dest, int flags) {
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
             dest.writeInt(mType);
             dest.writeInt(mEnterResId);
             dest.writeInt(mExitResId);
@@ -1247,6 +1274,7 @@
         }
 
         @Override
+        @NonNull
         public String toString() {
             final StringBuilder sb = new StringBuilder(32);
             sb.append("{t=").append(typeToString(mType));
@@ -1261,7 +1289,7 @@
         }
 
         /** Customized activity transition. */
-        public static class CustomActivityTransition implements Parcelable {
+        public static final class CustomActivityTransition implements Parcelable {
             private int mCustomEnterResId;
             private int mCustomExitResId;
             private int mCustomBackgroundColor;
@@ -1302,7 +1330,7 @@
             }
 
             @Override
-            public void writeToParcel(Parcel dest, int flags) {
+            public void writeToParcel(@NonNull Parcel dest, int flags) {
                 dest.writeInt(mCustomEnterResId);
                 dest.writeInt(mCustomExitResId);
                 dest.writeInt(mCustomBackgroundColor);
diff --git a/core/java/android/window/TransitionRequestInfo.java b/core/java/android/window/TransitionRequestInfo.java
index bd54e14..cc22576 100644
--- a/core/java/android/window/TransitionRequestInfo.java
+++ b/core/java/android/window/TransitionRequestInfo.java
@@ -113,7 +113,7 @@
 
     /** Requested change to a display. */
     @DataClass(genToString = true, genSetters = true, genBuilder = false, genConstructor = false)
-    public static class DisplayChange implements Parcelable {
+    public static final class DisplayChange implements Parcelable {
         private final int mDisplayId;
         @Nullable private Rect mStartAbsBounds = null;
         @Nullable private Rect mEndAbsBounds = null;
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index f94766e..0590c40 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -85,3 +85,10 @@
     description: "Matches the App Header density to that of the app window, instead of SysUI's"
     bug: "332414819"
 }
+
+flag {
+    name: "enable_themed_app_headers"
+    namespace: "lse_desktop_experience"
+    description: "Makes the App Header style adapt to the system's and app's light/dark theme"
+    bug: "328668781"
+}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index ee3e34f..f08f5b8 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -163,4 +163,12 @@
   metadata {
     purpose: PURPOSE_BUGFIX
   }
+}
+
+flag {
+    name: "release_snapshot_aggressively"
+    namespace: "windowing_frontend"
+    description: "Actively release task snapshot memory"
+    bug: "238206323"
+    is_fixed_read_only: true
 }
\ No newline at end of file
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 84715aa..17adee4 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -917,7 +917,7 @@
         mSystemWindowInsets = insets.getSystemWindowInsets();
 
         mResolverDrawerLayout.setPadding(mSystemWindowInsets.left, mSystemWindowInsets.top,
-                mSystemWindowInsets.right, 0);
+                mSystemWindowInsets.right, mSystemWindowInsets.bottom);
 
         resetButtonBar();
 
@@ -946,7 +946,7 @@
 
         if (mSystemWindowInsets != null) {
             mResolverDrawerLayout.setPadding(mSystemWindowInsets.left, mSystemWindowInsets.top,
-                    mSystemWindowInsets.right, 0);
+                    mSystemWindowInsets.right, mSystemWindowInsets.bottom);
         }
     }
 
diff --git a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
new file mode 100644
index 0000000..f306b0b
--- /dev/null
+++ b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.pm.pkg.component;
+
+import static com.android.internal.pm.pkg.parsing.ParsingUtils.ANDROID_RES_NAMESPACE;
+
+import android.aconfig.nano.Aconfig;
+import android.aconfig.nano.Aconfig.parsed_flag;
+import android.aconfig.nano.Aconfig.parsed_flags;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.Flags;
+import android.content.res.XmlResourceParser;
+import android.os.Environment;
+import android.os.Process;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.TypedXmlPullParser;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A class that manages a cache of all device feature flags and their default + override values.
+ * This class performs a very similar job to the one in {@code SettingsProvider}, with an important
+ * difference: this is a part of system server and is available for the server startup. Package
+ * parsing happens at the startup when {@code SettingsProvider} isn't available yet, so we need an
+ * own copy of the code here.
+ * @hide
+ */
+public class AconfigFlags {
+    private static final String LOG_TAG = "AconfigFlags";
+
+    private static final List<String> sTextProtoFilesOnDevice = List.of(
+            "/system/etc/aconfig_flags.pb",
+            "/system_ext/etc/aconfig_flags.pb",
+            "/product/etc/aconfig_flags.pb",
+            "/vendor/etc/aconfig_flags.pb");
+
+    private final ArrayMap<String, Boolean> mFlagValues = new ArrayMap<>();
+
+    public AconfigFlags() {
+        if (!Flags.manifestFlagging()) {
+            Slog.v(LOG_TAG, "Feature disabled, skipped all loading");
+            return;
+        }
+        for (String fileName : sTextProtoFilesOnDevice) {
+            try (var inputStream = new FileInputStream(fileName)) {
+                loadAconfigDefaultValues(inputStream.readAllBytes());
+            } catch (IOException e) {
+                Slog.e(LOG_TAG, "Failed to read Aconfig values from " + fileName, e);
+            }
+        }
+        if (Process.myUid() == Process.SYSTEM_UID) {
+            // Server overrides are only accessible to the system, no need to even try loading them
+            // in user processes.
+            loadServerOverrides();
+        }
+    }
+
+    private void loadServerOverrides() {
+        // Reading the proto files is enough for READ_ONLY flags but if it's a READ_WRITE flag
+        // (which you can check with `flag.getPermission() == flag_permission.READ_WRITE`) then we
+        // also need to check if there is a value pushed from the server in the file
+        // `/data/system/users/0/settings_config.xml`. It will be in a <setting> node under the
+        // root <settings> node with "name" attribute == "flag_namespace/flag_package.flag_name".
+        // The "value" attribute will be true or false.
+        //
+        // The "name" attribute could also be "<namespace>/flag_namespace?flag_package.flag_name"
+        // (prefixed with "staged/" or "device_config_overrides/" and a different separator between
+        // namespace and name). This happens when a flag value is overridden either with a pushed
+        // one from the server, or from the local command.
+        // When the device reboots during package parsing, the staged value will still be there and
+        // only later it will become a regular/non-staged value after SettingsProvider is
+        // initialized.
+        //
+        // In all cases, when there is more than one value, the priority is:
+        //      device_config_overrides > staged > default
+        //
+
+        final var settingsFile = new File(Environment.getUserSystemDirectory(0),
+                "settings_config.xml");
+        try (var inputStream = new FileInputStream(settingsFile)) {
+            TypedXmlPullParser parser = Xml.resolvePullParser(inputStream);
+            if (parser.next() != XmlPullParser.END_TAG && "settings".equals(parser.getName())) {
+                final var flagPriority = new ArrayMap<String, Integer>();
+                final int outerDepth = parser.getDepth();
+                int type;
+                while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                        && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+                    if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                        continue;
+                    }
+                    if (!"setting".equals(parser.getName())) {
+                        continue;
+                    }
+                    String name = parser.getAttributeValue(null, "name");
+                    final String value = parser.getAttributeValue(null, "value");
+                    if (name == null || value == null) {
+                        continue;
+                    }
+                    // A non-boolean setting is definitely not an Aconfig flag value.
+                    if (!"false".equalsIgnoreCase(value) && !"true".equalsIgnoreCase(value)) {
+                        continue;
+                    }
+                    final var overridePrefix = "device_config_overrides/";
+                    final var stagedPrefix = "staged/";
+                    String separator = "/";
+                    String prefix = "default";
+                    int priority = 0;
+                    if (name.startsWith(overridePrefix)) {
+                        prefix = overridePrefix;
+                        name = name.substring(overridePrefix.length());
+                        separator = ":";
+                        priority = 20;
+                    } else if (name.startsWith(stagedPrefix)) {
+                        prefix = stagedPrefix;
+                        name = name.substring(stagedPrefix.length());
+                        separator = "*";
+                        priority = 10;
+                    }
+                    final String flagPackageAndName = parseFlagPackageAndName(name, separator);
+                    if (flagPackageAndName == null) {
+                        continue;
+                    }
+                    // We ignore all settings that aren't for flags. We'll know they are for flags
+                    // if they correspond to flags read from the proto files.
+                    if (!mFlagValues.containsKey(flagPackageAndName)) {
+                        continue;
+                    }
+                    Slog.d(LOG_TAG, "Found " + prefix
+                            + " Aconfig flag value for " + flagPackageAndName + " = " + value);
+                    final Integer currentPriority = flagPriority.get(flagPackageAndName);
+                    if (currentPriority != null && currentPriority >= priority) {
+                        Slog.i(LOG_TAG, "Skipping " + prefix + " flag " + flagPackageAndName
+                                + " because of the existing one with priority " + currentPriority);
+                        continue;
+                    }
+                    flagPriority.put(flagPackageAndName, priority);
+                    mFlagValues.put(flagPackageAndName, Boolean.parseBoolean(value));
+                }
+            }
+        } catch (IOException | XmlPullParserException e) {
+            Slog.e(LOG_TAG, "Failed to read Aconfig values from settings_config.xml", e);
+        }
+    }
+
+    private static String parseFlagPackageAndName(String fullName, String separator) {
+        int index = fullName.indexOf(separator);
+        if (index < 0) {
+            return null;
+        }
+        return fullName.substring(index + 1);
+    }
+
+    private void loadAconfigDefaultValues(byte[] fileContents) throws IOException {
+        parsed_flags parsedFlags = parsed_flags.parseFrom(fileContents);
+        for (parsed_flag flag : parsedFlags.parsedFlag) {
+            String flagPackageAndName = flag.package_ + "." + flag.name;
+            boolean flagValue = (flag.state == Aconfig.ENABLED);
+            Slog.v(LOG_TAG, "Read Aconfig default flag value "
+                    + flagPackageAndName + " = " + flagValue);
+            mFlagValues.put(flagPackageAndName, flagValue);
+        }
+    }
+
+    /**
+     * Get the flag value, or null if the flag doesn't exist.
+     * @param flagPackageAndName Full flag name formatted as 'package.flag'
+     * @return the current value of the given Aconfig flag, or null if there is no such flag
+     */
+    @Nullable
+    public Boolean getFlagValue(@NonNull String flagPackageAndName) {
+        Boolean value = mFlagValues.get(flagPackageAndName);
+        Slog.d(LOG_TAG, "Aconfig flag value for " + flagPackageAndName + " = " + value);
+        return value;
+    }
+
+    /**
+     * Check if the element in {@code parser} should be skipped because of the feature flag.
+     * @param parser XML parser object currently parsing an element
+     * @return true if the element is disabled because of its feature flag
+     */
+    public boolean skipCurrentElement(@NonNull XmlResourceParser parser) {
+        if (!Flags.manifestFlagging()) {
+            return false;
+        }
+        String featureFlag = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "featureFlag");
+        if (featureFlag == null) {
+            return false;
+        }
+        featureFlag = featureFlag.strip();
+        boolean negated = false;
+        if (featureFlag.startsWith("!")) {
+            negated = true;
+            featureFlag = featureFlag.substring(1).strip();
+        }
+        final Boolean flagValue = getFlagValue(featureFlag);
+        if (flagValue == null) {
+            Slog.w(LOG_TAG, "Skipping element " + parser.getName()
+                    + " due to unknown feature flag " + featureFlag);
+            return true;
+        }
+        // Skip if flag==false && attr=="flag" OR flag==true && attr=="!flag" (negated)
+        if (flagValue == negated) {
+            Slog.v(LOG_TAG, "Skipping element " + parser.getName()
+                    + " behind feature flag " + featureFlag + " = " + flagValue);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Add Aconfig flag values for testing flagging of manifest entries.
+     * @param flagValues A map of flag name -> value.
+     */
+    @VisibleForTesting
+    public void addFlagValuesForTesting(@NonNull Map<String, Boolean> flagValues) {
+        mFlagValues.putAll(flagValues);
+    }
+}
diff --git a/core/java/com/android/internal/pm/pkg/component/ComponentParseUtils.java b/core/java/com/android/internal/pm/pkg/component/ComponentParseUtils.java
index db08005..8858f94 100644
--- a/core/java/com/android/internal/pm/pkg/component/ComponentParseUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ComponentParseUtils.java
@@ -61,6 +61,9 @@
             if (type != XmlPullParser.START_TAG) {
                 continue;
             }
+            if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
+                continue;
+            }
 
             final ParseResult result;
             if ("meta-data".equals(parser.getName())) {
diff --git a/core/java/com/android/internal/pm/pkg/component/InstallConstraintsTagParser.java b/core/java/com/android/internal/pm/pkg/component/InstallConstraintsTagParser.java
index 0b04591..bb01581 100644
--- a/core/java/com/android/internal/pm/pkg/component/InstallConstraintsTagParser.java
+++ b/core/java/com/android/internal/pm/pkg/component/InstallConstraintsTagParser.java
@@ -27,6 +27,7 @@
 
 import com.android.internal.R;
 import com.android.internal.pm.pkg.parsing.ParsingPackage;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -80,6 +81,9 @@
                 }
                 return input.success(prefixes);
             } else if (type == XmlPullParser.START_TAG) {
+                if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
+                    continue;
+                }
                 if (parser.getName().equals(TAG_FINGERPRINT_PREFIX)) {
                     ParseResult<String> parsedPrefix =
                             readFingerprintPrefixValue(input, res, parser);
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java
index 9f71d88..55baa53 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java
@@ -393,6 +393,9 @@
             if (type != XmlPullParser.START_TAG) {
                 continue;
             }
+            if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
+                continue;
+            }
 
             final ParseResult result;
             if (parser.getName().equals("intent-filter")) {
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java
index 05728ee..da48b23 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java
@@ -99,6 +99,9 @@
             if (type != XmlPullParser.START_TAG) {
                 continue;
             }
+            if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
+                continue;
+            }
 
             final ParseResult result;
             String nodeName = parser.getName();
@@ -197,6 +200,9 @@
             if (type != XmlPullParser.START_TAG) {
                 continue;
             }
+            if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
+                continue;
+            }
 
             final ParseResult result;
             String nodeName = parser.getName();
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java
index 12aff1c..6af2a29 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java
@@ -36,6 +36,7 @@
 
 import com.android.internal.R;
 import com.android.internal.pm.pkg.parsing.ParsingPackage;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.internal.pm.pkg.parsing.ParsingUtils;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -173,6 +174,9 @@
             if (type != XmlPullParser.START_TAG) {
                 continue;
             }
+            if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
+                continue;
+            }
 
             String name = parser.getName();
             final ParseResult result;
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java
index 4ac542f8..c68ea2d 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java
@@ -34,6 +34,7 @@
 
 import com.android.internal.R;
 import com.android.internal.pm.pkg.parsing.ParsingPackage;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.internal.pm.pkg.parsing.ParsingUtils;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -137,6 +138,9 @@
             if (type != XmlPullParser.START_TAG) {
                 continue;
             }
+            if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) {
+                continue;
+            }
 
             final ParseResult parseResult;
             switch (parser.getName()) {
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
index 1dcd893..44fedb1 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
@@ -90,6 +90,7 @@
 import com.android.internal.os.ClassLoaderFactory;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.internal.pm.permission.CompatibilityPermissionInfo;
+import com.android.internal.pm.pkg.component.AconfigFlags;
 import com.android.internal.pm.pkg.component.ComponentMutateUtils;
 import com.android.internal.pm.pkg.component.ComponentParseUtils;
 import com.android.internal.pm.pkg.component.InstallConstraintsTagParser;
@@ -292,6 +293,7 @@
     @NonNull
     private final List<PermissionManager.SplitPermissionInfo> mSplitPermissionInfos;
     private final Callback mCallback;
+    private static final AconfigFlags sAconfigFlags = new AconfigFlags();
 
     public ParsingPackageUtils(String[] separateProcesses, DisplayMetrics displayMetrics,
             @NonNull List<PermissionManager.SplitPermissionInfo> splitPermissions,
@@ -761,6 +763,9 @@
             if (outerDepth + 1 < parser.getDepth() || type != XmlPullParser.START_TAG) {
                 continue;
             }
+            if (sAconfigFlags.skipCurrentElement(parser)) {
+                continue;
+            }
 
             final ParseResult result;
             String tagName = parser.getName();
@@ -837,6 +842,9 @@
             if (type != XmlPullParser.START_TAG) {
                 continue;
             }
+            if (sAconfigFlags.skipCurrentElement(parser)) {
+                continue;
+            }
 
             ParsedMainComponent mainComponent = null;
 
@@ -980,6 +988,9 @@
             if (type != XmlPullParser.START_TAG) {
                 continue;
             }
+            if (sAconfigFlags.skipCurrentElement(parser)) {
+                continue;
+            }
 
             String tagName = parser.getName();
             final ParseResult result;
@@ -1599,6 +1610,9 @@
             if (type != XmlPullParser.START_TAG) {
                 continue;
             }
+            if (sAconfigFlags.skipCurrentElement(parser)) {
+                continue;
+            }
 
             final String innerTagName = parser.getName();
             if (innerTagName.equals("uses-feature")) {
@@ -1839,6 +1853,9 @@
             if (type != XmlPullParser.START_TAG) {
                 continue;
             }
+            if (sAconfigFlags.skipCurrentElement(parser)) {
+                continue;
+            }
             if (parser.getName().equals("intent")) {
                 ParseResult<ParsedIntentInfoImpl> result = ParsedIntentInfoUtils.parseIntentInfo(
                         null /*className*/, pkg, res, parser, true /*allowGlobs*/,
@@ -2185,6 +2202,9 @@
             if (type != XmlPullParser.START_TAG) {
                 continue;
             }
+            if (sAconfigFlags.skipCurrentElement(parser)) {
+                continue;
+            }
 
             final ParseResult result;
             String tagName = parser.getName();
@@ -2773,6 +2793,9 @@
             if (type != XmlPullParser.START_TAG) {
                 continue;
             }
+            if (sAconfigFlags.skipCurrentElement(parser)) {
+                continue;
+            }
 
             final String nodeName = parser.getName();
             if (nodeName.equals("additional-certificate")) {
@@ -3458,4 +3481,11 @@
 
         @NonNull Set<String> getInstallConstraintsAllowlist();
     }
+
+    /**
+     * Getter for the flags object
+     */
+    public static AconfigFlags getAconfigFlags() {
+        return sAconfigFlags;
+    }
 }
diff --git a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
index 1340156..4a3dfbe 100644
--- a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
+++ b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
@@ -49,7 +49,7 @@
         return false;
     }
 
-    public boolean isRunningOnRavenwood$ravenwood() {
+    private boolean isRunningOnRavenwood$ravenwood() {
         return true;
     }
 }
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index d9c40c3..61eaa52 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -439,6 +439,7 @@
                 "android_database_SQLiteConnection.cpp",
                 "android_database_SQLiteGlobal.cpp",
                 "android_database_SQLiteDebug.cpp",
+                "android_database_SQLiteRawStatement.cpp",
                 "android_hardware_input_InputApplicationHandle.cpp",
                 "android_os_MessageQueue.cpp",
                 "android_os_Parcel.cpp",
diff --git a/core/jni/platform/host/HostRuntime.cpp b/core/jni/platform/host/HostRuntime.cpp
index acef609..59d18b8 100644
--- a/core/jni/platform/host/HostRuntime.cpp
+++ b/core/jni/platform/host/HostRuntime.cpp
@@ -89,6 +89,7 @@
 extern int register_android_database_SQLiteConnection(JNIEnv* env);
 extern int register_android_database_SQLiteGlobal(JNIEnv* env);
 extern int register_android_database_SQLiteDebug(JNIEnv* env);
+extern int register_android_database_SQLiteRawStatement(JNIEnv* env);
 extern int register_android_os_FileObserver(JNIEnv* env);
 extern int register_android_os_MessageQueue(JNIEnv* env);
 extern int register_android_os_Parcel(JNIEnv* env);
@@ -128,6 +129,8 @@
          REG_JNI(register_android_database_SQLiteConnection)},
         {"android.database.sqlite.SQLiteGlobal", REG_JNI(register_android_database_SQLiteGlobal)},
         {"android.database.sqlite.SQLiteDebug", REG_JNI(register_android_database_SQLiteDebug)},
+        {"android.database.sqlite.SQLiteRawStatement",
+         REG_JNI(register_android_database_SQLiteRawStatement)},
 #endif
         {"android.content.res.StringBlock", REG_JNI(register_android_content_StringBlock)},
         {"android.content.res.XmlBlock", REG_JNI(register_android_content_XmlBlock)},
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 5fa13ba..405324b 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2589,6 +2589,8 @@
                  <li>The framework will set {@link android.R.attr#statusBarColor},
                  {@link android.R.attr#navigationBarColor}, and
                  {@link android.R.attr#navigationBarDividerColor} to transparent.
+                 <li>The frameworks will send Configuration no longer considering system insets.
+                 The Configuration will be stable regardless of the system insets change.
              </ul>
 
              <p>If this is true, the edge-to-edge enforcement won't be applied. However, this
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index 4d7c009..67cceb5 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -60,6 +60,9 @@
     <!-- Belgium: 4 digits, plus EU: http://www.mobileweb.be/en/mobileweb/sms-numberplan.asp -->
     <shortcode country="be" premium="\\d{4}" free="8\\d{3}|116\\d{3}" />
 
+    <!-- Burkina Faso: 1-4 digits (standard system default, not country specific) -->
+    <shortcode country="bf" pattern="\\d{1,4}" free="3558" />
+
     <!-- Bulgaria: 4-5 digits, plus EU -->
     <shortcode country="bg" pattern="\\d{4,5}" premium="18(?:16|423)|19(?:1[56]|35)" free="116\\d{3}|1988|1490" />
 
@@ -175,8 +178,8 @@
     <!-- Israel: 1-5 digits, known premium codes listed -->
     <shortcode country="il" pattern="\\d{1,5}" premium="4422|4545" free="37477|6681" />
 
-    <!-- Iran: 4-6 digits, known premium codes listed -->
-    <shortcode country="ir" pattern="\\d{4,6}" free="700791|700792" />
+    <!-- Iran: 4-8 digits, known premium codes listed -->
+    <shortcode country="ir" pattern="\\d{4,8}" free="700791|700792|100016|30008360" />
 
     <!-- Italy: 5 digits (premium=41xxx,42xxx), plus EU:
          https://www.itu.int/dms_pub/itu-t/oth/02/02/T020200006B0001PDFE.pdf -->
@@ -352,7 +355,7 @@
     <shortcode country="za" pattern="\\d{1,5}" free="44136|30791|36056|33009" />
 
     <!-- Yemen -->
-    <shortcode country="ye" pattern="\\d{1,4}" free="5081" />
+    <shortcode country="ye" pattern="\\d{1,4}" free="5079" />
 
     <!-- Zimbabwe -->
     <shortcode country="zw" pattern="\\d{1,5}" free="33679" />
diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java
index bd13276..5a7b0bb 100644
--- a/graphics/java/android/graphics/fonts/FontFamily.java
+++ b/graphics/java/android/graphics/fonts/FontFamily.java
@@ -25,6 +25,7 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.text.FontConfig;
 import android.util.SparseIntArray;
 
@@ -151,6 +152,7 @@
          * @return A variable font family. null if a variable font cannot be built from the given
          *         fonts.
          */
+        @SuppressLint("BuilderSetStyle")
         @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML)
         public @Nullable FontFamily buildVariableFamily() {
             int variableFamilyType = analyzeAndResolveVariableType(mFonts);
diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java
index 7d55928..5a1086c 100644
--- a/graphics/java/android/graphics/text/LineBreakConfig.java
+++ b/graphics/java/android/graphics/text/LineBreakConfig.java
@@ -23,6 +23,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.app.ActivityThread;
 import android.os.Build;
 import android.os.LocaleList;
@@ -314,6 +315,7 @@
          * @param config an override line break config
          * @return This {@code Builder}.
          */
+        @SuppressLint("BuilderSetStyle")
         @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
         public @NonNull Builder merge(@NonNull LineBreakConfig config) {
             if (config.mLineBreakStyle != LINE_BREAK_STYLE_UNSPECIFIED) {
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
index 785e30d..6ca6517 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
@@ -322,7 +322,7 @@
                 null,
                 new Rect(change.getStartAbsBounds()),
                 taskInfo,
-                change.getAllowEnterPip(),
+                change.isAllowEnterPip(),
                 INVALID_WINDOW_TYPE
         );
         target.setWillShowImeOnTarget(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index 01364d1..6968317 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -46,6 +46,7 @@
 import com.android.wm.shell.pip2.phone.PipTransition;
 import com.android.wm.shell.pip2.phone.PipTransitionState;
 import com.android.wm.shell.shared.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
@@ -82,6 +83,7 @@
     @Provides
     static Optional<PipController> providePipController(Context context,
             ShellInit shellInit,
+            ShellCommandHandler shellCommandHandler,
             ShellController shellController,
             DisplayController displayController,
             DisplayInsetsController displayInsetsController,
@@ -97,9 +99,10 @@
             return Optional.empty();
         } else {
             return Optional.ofNullable(PipController.create(
-                    context, shellInit, shellController, displayController, displayInsetsController,
-                    pipBoundsState, pipBoundsAlgorithm, pipDisplayLayoutState, pipScheduler,
-                    taskStackListener, shellTaskOrganizer, pipTransitionState, mainExecutor));
+                    context, shellInit, shellCommandHandler, shellController, displayController,
+                    displayInsetsController, pipBoundsState, pipBoundsAlgorithm,
+                    pipDisplayLayoutState, pipScheduler, taskStackListener, shellTaskOrganizer,
+                    pipTransitionState, mainExecutor));
         }
     }
 
@@ -129,6 +132,7 @@
     @Provides
     static PipTouchHandler providePipTouchHandler(Context context,
             ShellInit shellInit,
+            ShellCommandHandler shellCommandHandler,
             PhonePipMenuController menuPhoneController,
             PipBoundsAlgorithm pipBoundsAlgorithm,
             @NonNull PipBoundsState pipBoundsState,
@@ -140,10 +144,10 @@
             PipUiEventLogger pipUiEventLogger,
             @ShellMainThread ShellExecutor mainExecutor,
             Optional<PipPerfHintController> pipPerfHintControllerOptional) {
-        return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm,
-                pipBoundsState, pipTransitionState, pipScheduler, sizeSpecSource, pipMotionHelper,
-                floatingContentCoordinator, pipUiEventLogger, mainExecutor,
-                pipPerfHintControllerOptional);
+        return new PipTouchHandler(context, shellInit, shellCommandHandler, menuPhoneController,
+                pipBoundsAlgorithm, pipBoundsState, pipTransitionState, pipScheduler,
+                sizeSpecSource, pipMotionHelper, floatingContentCoordinator, pipUiEventLogger,
+                mainExecutor, pipPerfHintControllerOptional);
     }
 
     @WMSingleton
@@ -163,7 +167,7 @@
 
     @WMSingleton
     @Provides
-    static PipTransitionState providePipStackListenerController() {
-        return new PipTransitionState();
+    static PipTransitionState providePipTransitionState(@ShellMainThread Handler handler) {
+        return new PipTransitionState(handler);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index 6a7d297..a42ca19 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -16,10 +16,9 @@
 
 package com.android.wm.shell.draganddrop;
 
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
 import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED;
-import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.content.ClipDescription.EXTRA_ACTIVITY_OPTIONS;
@@ -47,7 +46,6 @@
 import android.app.ActivityTaskManager;
 import android.app.PendingIntent;
 import android.content.ActivityNotFoundException;
-import android.content.ClipData;
 import android.content.ClipDescription;
 import android.content.Context;
 import android.content.Intent;
@@ -265,13 +263,14 @@
         final boolean isShortcut = description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT);
         final ActivityOptions baseActivityOpts = ActivityOptions.makeBasic();
         baseActivityOpts.setDisallowEnterPictureInPictureWhileLaunching(true);
+        // Put BAL flags to avoid activity start aborted.
+        baseActivityOpts.setPendingIntentBackgroundActivityStartMode(
+                MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+        baseActivityOpts.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true);
         final Bundle opts = baseActivityOpts.toBundle();
         if (session.appData.hasExtra(EXTRA_ACTIVITY_OPTIONS)) {
             opts.putAll(session.appData.getBundleExtra(EXTRA_ACTIVITY_OPTIONS));
         }
-        // Put BAL flags to avoid activity start aborted.
-        opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true);
-        opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, true);
         final UserHandle user = session.appData.getParcelableExtra(EXTRA_USER);
 
         if (isTask) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index a12882f..f5afeea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -58,9 +58,12 @@
 import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.sysui.ConfigurationChangeListener;
+import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 
+import java.io.PrintWriter;
+
 /**
  * Manages the picture-in-picture (PIP) UI and states for Phones.
  */
@@ -72,6 +75,7 @@
     private static final String SWIPE_TO_PIP_OVERLAY = "swipe_to_pip_overlay";
 
     private final Context mContext;
+    private final ShellCommandHandler mShellCommandHandler;
     private final ShellController mShellController;
     private final DisplayController mDisplayController;
     private final DisplayInsetsController mDisplayInsetsController;
@@ -111,6 +115,7 @@
 
     private PipController(Context context,
             ShellInit shellInit,
+            ShellCommandHandler shellCommandHandler,
             ShellController shellController,
             DisplayController displayController,
             DisplayInsetsController displayInsetsController,
@@ -123,6 +128,7 @@
             PipTransitionState pipTransitionState,
             ShellExecutor mainExecutor) {
         mContext = context;
+        mShellCommandHandler = shellCommandHandler;
         mShellController = shellController;
         mDisplayController = displayController;
         mDisplayInsetsController = displayInsetsController;
@@ -146,6 +152,7 @@
      */
     public static PipController create(Context context,
             ShellInit shellInit,
+            ShellCommandHandler shellCommandHandler,
             ShellController shellController,
             DisplayController displayController,
             DisplayInsetsController displayInsetsController,
@@ -162,13 +169,14 @@
                     "%s: Device doesn't support Pip feature", TAG);
             return null;
         }
-        return new PipController(context, shellInit, shellController, displayController,
-                displayInsetsController, pipBoundsState, pipBoundsAlgorithm, pipDisplayLayoutState,
-                pipScheduler, taskStackListener, shellTaskOrganizer, pipTransitionState,
-                mainExecutor);
+        return new PipController(context, shellInit, shellCommandHandler, shellController,
+                displayController, displayInsetsController, pipBoundsState, pipBoundsAlgorithm,
+                pipDisplayLayoutState, pipScheduler, taskStackListener, shellTaskOrganizer,
+                pipTransitionState, mainExecutor);
     }
 
     private void onInit() {
+        mShellCommandHandler.addDumpCallback(this::dump, this);
         // Ensure that we have the display info in case we get calls to update the bounds before the
         // listener calls back
         mPipDisplayLayoutState.setDisplayId(mContext.getDisplayId());
@@ -338,6 +346,14 @@
         }
     }
 
+    private void dump(PrintWriter pw, String prefix) {
+        final String innerPrefix = "  ";
+        pw.println(TAG);
+        mPipBoundsAlgorithm.dump(pw, innerPrefix);
+        mPipBoundsState.dump(pw, innerPrefix);
+        mPipDisplayLayoutState.dump(pw, innerPrefix);
+    }
+
     /**
      * The interface for calls from outside the host process.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
index be10151..aed493f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
@@ -696,6 +696,19 @@
             case PipTransitionState.SCHEDULED_BOUNDS_CHANGE:
                 if (!extra.getBoolean(FLING_BOUNDS_CHANGE)) break;
 
+                if (mPipBoundsState.getBounds().equals(
+                        mPipBoundsState.getMotionBoundsState().getBoundsInMotion())) {
+                    // Avoid scheduling transitions for bounds that don't change, such transition is
+                    // a no-op and would be aborted.
+                    settlePipBoundsAfterPhysicsAnimation(false /* animatingAfter */);
+                    cleanUpHighPerfSessionMaybe();
+                    // SCHEDULED_BOUNDS_CHANGE can have multiple active listeners making
+                    // actual changes (e.g. PipTouchHandler). So post state update onto handler,
+                    // to run after synchronous dispatch is complete.
+                    mPipTransitionState.postState(PipTransitionState.CHANGED_PIP_BOUNDS);
+                    break;
+                }
+
                 // If touch is turned off and we are in a fling animation, schedule a transition.
                 mWaitingForBoundsChangeTransition = true;
                 mPipScheduler.scheduleAnimateResizePip(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
index b55a41d..7dffe54 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
@@ -514,6 +514,20 @@
         switch (newState) {
             case PipTransitionState.SCHEDULED_BOUNDS_CHANGE:
                 if (!extra.getBoolean(RESIZE_BOUNDS_CHANGE)) break;
+
+                if (mPipBoundsState.getBounds().equals(mLastResizeBounds)) {
+                    // If the bounds are invariant move the destination bounds by a single pixel
+                    // to top/bottom to avoid a no-op transition. This trick helps keep the
+                    // animation a part of the transition.
+                    float snapFraction = mPipBoundsAlgorithm.getSnapFraction(
+                            mPipBoundsState.getBounds());
+
+                    // Move to the top if closer to the bottom edge and vice versa.
+                    boolean inTopHalf = snapFraction < 1.5 || snapFraction > 3.5;
+                    int offsetY = inTopHalf ? 1 : -1;
+                    mLastResizeBounds.offset(0 /* dx */, offsetY);
+                }
+
                 mWaitingForBoundsChangeTransition = true;
                 mPipScheduler.scheduleAnimateResizePip(mLastResizeBounds);
                 break;
@@ -527,17 +541,14 @@
                         PipTransition.PIP_START_TX, SurfaceControl.Transaction.class);
                 Rect destinationBounds = extra.getParcelable(
                         PipTransition.PIP_DESTINATION_BOUNDS, Rect.class);
-                startTx.setPosition(mPipTransitionState.mPinnedTaskLeash,
-                        destinationBounds.left, destinationBounds.top);
                 startTx.apply();
 
                 // All motion operations have actually finished, so make bounds cache updates.
+                mUpdateResizeBoundsCallback.accept(destinationBounds);
                 cleanUpHighPerfSessionMaybe();
 
                 // Setting state to CHANGED_PIP_BOUNDS applies finishTx and notifies Core.
                 mPipTransitionState.setState(PipTransitionState.CHANGED_PIP_BOUNDS);
-
-                mUpdateResizeBoundsCallback.accept(destinationBounds);
                 break;
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
index 319d199..56a465a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
@@ -64,6 +64,7 @@
 import com.android.wm.shell.common.pip.SizeSpecSource;
 import com.android.wm.shell.pip.PipAnimationController;
 import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellInit;
 
 import java.io.PrintWriter;
@@ -81,6 +82,7 @@
     // Allow PIP to resize to a slightly bigger state upon touch
     private boolean mEnableResize;
     private final Context mContext;
+    private final ShellCommandHandler mShellCommandHandler;
     private final PipBoundsAlgorithm mPipBoundsAlgorithm;
     @NonNull private final PipBoundsState mPipBoundsState;
     @NonNull private final PipTransitionState mPipTransitionState;
@@ -170,6 +172,7 @@
     @SuppressLint("InflateParams")
     public PipTouchHandler(Context context,
             ShellInit shellInit,
+            ShellCommandHandler shellCommandHandler,
             PhonePipMenuController menuController,
             PipBoundsAlgorithm pipBoundsAlgorithm,
             @NonNull PipBoundsState pipBoundsState,
@@ -182,6 +185,7 @@
             ShellExecutor mainExecutor,
             Optional<PipPerfHintController> pipPerfHintControllerOptional) {
         mContext = context;
+        mShellCommandHandler = shellCommandHandler;
         mMainExecutor = mainExecutor;
         mPipPerfHintController = pipPerfHintControllerOptional.orElse(null);
         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
@@ -235,6 +239,7 @@
         mEnableResize = res.getBoolean(R.bool.config_pipEnableResizeForMenu);
         reloadResources();
 
+        mShellCommandHandler.addDumpCallback(this::dump, this);
         mMotionHelper.init();
         mPipResizeGestureHandler.init();
         mPipDismissTargetHandler.init();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
index 8204d41..9d599ca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.graphics.Rect;
 import android.os.Bundle;
+import android.os.Handler;
 import android.view.SurfaceControl;
 import android.window.WindowContainerToken;
 
@@ -26,6 +27,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.util.Preconditions;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -109,6 +111,13 @@
     private int mState;
 
     //
+    // Dependencies
+    //
+
+    @ShellMainThread
+    private final Handler mMainHandler;
+
+    //
     // Swipe up to enter PiP related state
     //
 
@@ -149,6 +158,10 @@
 
     private final List<PipTransitionStateChangedListener> mCallbacks = new ArrayList<>();
 
+    public PipTransitionState(@ShellMainThread Handler handler) {
+        mMainHandler = handler;
+    }
+
     /**
      * @return the state of PiP in the context of transitions.
      */
@@ -182,6 +195,32 @@
         }
     }
 
+    /**
+     * Posts the state update for PiP in the context of transitions onto the main handler.
+     *
+     * <p>This is done to guarantee that any callback dispatches for the present state are
+     * complete. This is relevant for states that have multiple listeners, such as
+     * <code>SCHEDULED_BOUNDS_CHANGE</code> that helps turn off touch interactions along with
+     * the actual transition scheduling.</p>
+     */
+    public void postState(@TransitionState int state) {
+        postState(state, null /* extra */);
+    }
+
+    /**
+     * Posts the state update for PiP in the context of transitions onto the main handler.
+     *
+     * <p>This is done to guarantee that any callback dispatches for the present state are
+     * complete. This is relevant for states that have multiple listeners, such as
+     * <code>SCHEDULED_BOUNDS_CHANGE</code> that helps turn off touch interactions along with
+     * the actual transition scheduling.</p>
+     *
+     * @param extra a bundle passed to the subscribed listeners to resolve/cache extra info.
+     */
+    public void postState(@TransitionState int state, @Nullable Bundle extra) {
+        mMainHandler.post(() -> setState(state, extra));
+    }
+
     private void dispatchPipTransitionStateChanged(@TransitionState int oldState,
             @TransitionState int newState, @Nullable Bundle extra) {
         mCallbacks.forEach(l -> l.onPipTransitionStateChanged(oldState, newState, extra));
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 4299088..9f8cb62 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
@@ -16,10 +16,8 @@
 
 package com.android.wm.shell.splitscreen;
 
-import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED;
-import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
@@ -48,7 +46,6 @@
 import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
 import static com.android.wm.shell.shared.TransitionUtil.isClosingType;
-import static com.android.wm.shell.shared.TransitionUtil.isOpeningMode;
 import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
@@ -1888,13 +1885,15 @@
     }
 
     private void addActivityOptions(Bundle opts, @Nullable StageTaskListener launchTarget) {
+        ActivityOptions options = ActivityOptions.fromBundle(opts);
         if (launchTarget != null) {
-            opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, launchTarget.mRootTaskInfo.token);
+            options.setLaunchRootTask(launchTarget.mRootTaskInfo.token);
         }
         // Put BAL flags to avoid activity start aborted. Otherwise, flows like shortcut to split
         // will be canceled.
-        opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true);
-        opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, true);
+        options.setPendingIntentBackgroundActivityStartMode(MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+        options.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true);
+        opts.putAll(options.toBundle());
     }
 
     void updateActivityOptions(Bundle opts, @SplitPosition int position) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 1be3b02..8a49a73 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -93,8 +93,7 @@
             Choreographer choreographer,
             SyncTransactionQueue syncQueue,
             ResizeHandleSizeRepository resizeHandleSizeRepository) {
-        super(context, displayController, taskOrganizer, taskInfo, taskSurface,
-                taskInfo.getConfiguration());
+        super(context, displayController, taskOrganizer, taskInfo, taskSurface);
 
         mHandler = handler;
         mChoreographer = choreographer;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index bb89adf..9c92791 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -140,13 +140,12 @@
             ShellTaskOrganizer taskOrganizer,
             ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl taskSurface,
-            Configuration windowDecorConfig,
             Handler handler,
             Choreographer choreographer,
             SyncTransactionQueue syncQueue,
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
             ResizeHandleSizeRepository resizeHandleSizeRepository) {
-        this (context, displayController, taskOrganizer, taskInfo, taskSurface, windowDecorConfig,
+        this (context, displayController, taskOrganizer, taskInfo, taskSurface,
                 handler, choreographer, syncQueue, rootTaskDisplayAreaOrganizer,
                 resizeHandleSizeRepository, SurfaceControl.Builder::new,
                 SurfaceControl.Transaction::new, WindowContainerTransaction::new,
@@ -159,7 +158,6 @@
             ShellTaskOrganizer taskOrganizer,
             ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl taskSurface,
-            Configuration windowDecorConfig,
             Handler handler,
             Choreographer choreographer,
             SyncTransactionQueue syncQueue,
@@ -170,7 +168,7 @@
             Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
             Supplier<SurfaceControl> surfaceControlSupplier,
             SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
-        super(context, displayController, taskOrganizer, taskInfo, taskSurface, windowDecorConfig,
+        super(context, displayController, taskOrganizer, taskInfo, taskSurface,
                 surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
                 windowContainerTransactionSupplier, surfaceControlSupplier,
                 surfaceControlViewHostFactory);
@@ -964,17 +962,12 @@
                 SyncTransactionQueue syncQueue,
                 RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
                 ResizeHandleSizeRepository resizeHandleSizeRepository) {
-            final Configuration windowDecorConfig =
-                    DesktopModeStatus.isDesktopDensityOverrideSet()
-                    ? context.getResources().getConfiguration() // Use system context
-                    : taskInfo.configuration; // Use task configuration
             return new DesktopModeWindowDecoration(
                     context,
                     displayController,
                     taskOrganizer,
                     taskInfo,
                     taskSurface,
-                    windowDecorConfig,
                     handler,
                     choreographer,
                     syncQueue,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 5418254..2cbe472 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -18,6 +18,7 @@
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.res.Configuration.DENSITY_DPI_UNDEFINED;
 import static android.view.WindowInsets.Type.captionBar;
 import static android.view.WindowInsets.Type.mandatorySystemGestures;
 import static android.view.WindowInsets.Type.statusBars;
@@ -145,9 +146,8 @@
             DisplayController displayController,
             ShellTaskOrganizer taskOrganizer,
             RunningTaskInfo taskInfo,
-            SurfaceControl taskSurface,
-            Configuration windowDecorConfig) {
-        this(context, displayController, taskOrganizer, taskInfo, taskSurface, windowDecorConfig,
+            SurfaceControl taskSurface) {
+        this(context, displayController, taskOrganizer, taskInfo, taskSurface,
                 SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
                 WindowContainerTransaction::new, SurfaceControl::new,
                 new SurfaceControlViewHostFactory() {});
@@ -159,7 +159,6 @@
             ShellTaskOrganizer taskOrganizer,
             RunningTaskInfo taskInfo,
             @NonNull SurfaceControl taskSurface,
-            Configuration windowDecorConfig,
             Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
             Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
             Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
@@ -176,8 +175,6 @@
         mSurfaceControlViewHostFactory = surfaceControlViewHostFactory;
 
         mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId);
-        mWindowDecorConfig = windowDecorConfig;
-        mDecorWindowContext = mContext.createConfigurationContext(mWindowDecorConfig);
     }
 
     /**
@@ -220,8 +217,11 @@
         outResult.mRootView = rootView;
         rootView = null; // Clear it just in case we use it accidentally
 
-        final int oldDensityDpi = mWindowDecorConfig.densityDpi;
-        final int oldNightMode = mWindowDecorConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK;
+        final int oldDensityDpi = mWindowDecorConfig != null
+                ? mWindowDecorConfig.densityDpi : DENSITY_DPI_UNDEFINED;
+        final int oldNightMode =  mWindowDecorConfig != null
+                ? (mWindowDecorConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK)
+                : Configuration.UI_MODE_NIGHT_UNDEFINED;
         mWindowDecorConfig = params.mWindowDecorConfig != null ? params.mWindowDecorConfig
                 : mTaskInfo.getConfiguration();
         final int newDensityDpi = mWindowDecorConfig.densityDpi;
@@ -230,7 +230,8 @@
                 || mDisplay == null
                 || mDisplay.getDisplayId() != mTaskInfo.displayId
                 || oldLayoutResId != mLayoutResId
-                || oldNightMode != newNightMode) {
+                || oldNightMode != newNightMode
+                || mDecorWindowContext == null) {
             releaseViews(wct);
 
             if (!obtainDisplayOrRegisterListener()) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/PipTransitionStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/PipTransitionStateTest.java
index bd8ac37..f3f3c37 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/PipTransitionStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/PipTransitionStateTest.java
@@ -17,6 +17,7 @@
 package com.android.wm.shell.pip2;
 
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.Parcelable;
 import android.testing.AndroidTestingRunner;
 
@@ -29,6 +30,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
 
 /**
  * Unit test against {@link PhoneSizeSpecSource}.
@@ -42,9 +44,12 @@
     private PipTransitionState.PipTransitionStateChangedListener mStateChangedListener;
     private Parcelable mEmptyParcelable;
 
+    @Mock
+    private Handler mMainHandler;
+
     @Before
     public void setUp() {
-        mPipTransitionState = new PipTransitionState();
+        mPipTransitionState = new PipTransitionState(mMainHandler);
         mPipTransitionState.setState(PipTransitionState.UNDEFINED);
         mEmptyParcelable = new Bundle();
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index d7c3835..d18fec2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -16,10 +16,7 @@
 
 package com.android.wm.shell.splitscreen;
 
-import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED;
-import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
@@ -31,8 +28,9 @@
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.notNull;
@@ -45,6 +43,7 @@
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
+import android.app.ActivityOptions;
 import android.app.PendingIntent;
 import android.content.res.Configuration;
 import android.graphics.Rect;
@@ -54,7 +53,6 @@
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 import android.window.RemoteTransition;
-import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
 import androidx.test.annotation.UiThreadTest;
@@ -343,14 +341,14 @@
 
     @Test
     public void testAddActivityOptions_addsBackgroundActivitiesFlags() {
-        Bundle options = mStageCoordinator.resolveStartStage(STAGE_TYPE_MAIN,
+        Bundle bundle = mStageCoordinator.resolveStartStage(STAGE_TYPE_MAIN,
                 SPLIT_POSITION_UNDEFINED, null /* options */, null /* wct */);
+        ActivityOptions options = ActivityOptions.fromBundle(bundle);
 
-        assertEquals(options.getParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, WindowContainerToken.class),
-                mMainStage.mRootTaskInfo.token);
-        assertTrue(options.getBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED));
-        assertTrue(options.getBoolean(
-                KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION));
+        assertThat(options.getLaunchRootTask()).isEqualTo(mMainStage.mRootTaskInfo.token);
+        assertThat(options.getPendingIntentBackgroundActivityStartMode())
+                .isEqualTo(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+        assertThat(options.isPendingIntentBackgroundActivityLaunchAllowedByPermission()).isTrue();
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index cff9313..e737861 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -346,7 +346,7 @@
     private DesktopModeWindowDecoration createWindowDecoration(
             ActivityManager.RunningTaskInfo taskInfo) {
         return new DesktopModeWindowDecoration(mContext, mMockDisplayController,
-                mMockShellTaskOrganizer, taskInfo, mMockSurfaceControl, mConfiguration,
+                mMockShellTaskOrganizer, taskInfo, mMockSurfaceControl,
                 mMockHandler, mMockChoreographer, mMockSyncQueue, mMockRootTaskDisplayAreaOrganizer,
                 mMockResizeHandleSizeRepository, SurfaceControl.Builder::new,
                 mMockTransactionSupplier, WindowContainerTransaction::new, SurfaceControl::new,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 8b8cd11..4831081 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -49,7 +49,6 @@
 
 import android.app.ActivityManager;
 import android.content.Context;
-import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.Point;
@@ -134,7 +133,6 @@
     private SurfaceControl.Transaction mMockSurfaceControlFinishT;
     private SurfaceControl.Transaction mMockSurfaceControlAddWindowT;
     private WindowDecoration.RelayoutParams mRelayoutParams = new WindowDecoration.RelayoutParams();
-    private Configuration mWindowConfiguration = new Configuration();
     private int mCaptionMenuWidthId;
 
     @Before
@@ -303,7 +301,6 @@
         taskInfo.isFocused = true;
         // Density is 2. Shadow radius is 10px. Caption height is 64px.
         taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
-        mWindowConfiguration.densityDpi = taskInfo.configuration.densityDpi;
 
         final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
 
@@ -314,14 +311,16 @@
         verify(mMockWindowContainerTransaction, never())
                 .removeInsetsSource(eq(taskInfo.token), any(), anyInt(), anyInt());
 
+        final SurfaceControl.Transaction t2 = mock(SurfaceControl.Transaction.class);
+        mMockSurfaceControlTransactions.add(t2);
         taskInfo.isVisible = false;
         windowDecor.relayout(taskInfo);
 
-        final InOrder releaseOrder = inOrder(t, mMockSurfaceControlViewHost);
+        final InOrder releaseOrder = inOrder(t2, mMockSurfaceControlViewHost);
         releaseOrder.verify(mMockSurfaceControlViewHost).release();
-        releaseOrder.verify(t).remove(captionContainerSurface);
-        releaseOrder.verify(t).remove(decorContainerSurface);
-        releaseOrder.verify(t).apply();
+        releaseOrder.verify(t2).remove(captionContainerSurface);
+        releaseOrder.verify(t2).remove(decorContainerSurface);
+        releaseOrder.verify(t2).apply();
         // Expect to remove two insets sources, the caption insets and the mandatory gesture insets.
         verify(mMockWindowContainerTransaction, Mockito.times(2))
                 .removeInsetsSource(eq(taskInfo.token), any(), anyInt(), anyInt());
@@ -836,7 +835,7 @@
 
     private TestWindowDecoration createWindowDecoration(ActivityManager.RunningTaskInfo taskInfo) {
         return new TestWindowDecoration(mContext, mMockDisplayController, mMockShellTaskOrganizer,
-                taskInfo, mMockTaskSurface, mWindowConfiguration,
+                taskInfo, mMockTaskSurface,
                 new MockObjectSupplier<>(mMockSurfaceControlBuilders,
                         () -> createMockSurfaceControlBuilder(mock(SurfaceControl.class))),
                 new MockObjectSupplier<>(mMockSurfaceControlTransactions,
@@ -877,16 +876,15 @@
         TestWindowDecoration(Context context, DisplayController displayController,
                 ShellTaskOrganizer taskOrganizer, ActivityManager.RunningTaskInfo taskInfo,
                 SurfaceControl taskSurface,
-                Configuration windowConfiguration,
                 Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
                 Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
                 Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
                 Supplier<SurfaceControl> surfaceControlSupplier,
                 SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
             super(context, displayController, taskOrganizer, taskInfo, taskSurface,
-                    windowConfiguration, surfaceControlBuilderSupplier,
-                    surfaceControlTransactionSupplier, windowContainerTransactionSupplier,
-                    surfaceControlSupplier, surfaceControlViewHostFactory);
+                    surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
+                    windowContainerTransactionSupplier, surfaceControlSupplier,
+                    surfaceControlViewHostFactory);
         }
 
         @Override
diff --git a/libs/hwui/jni/BitmapFactory.cpp b/libs/hwui/jni/BitmapFactory.cpp
index 3d0a534..785aef3 100644
--- a/libs/hwui/jni/BitmapFactory.cpp
+++ b/libs/hwui/jni/BitmapFactory.cpp
@@ -688,8 +688,8 @@
 
 static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz, jobject fileDescriptor,
         jobject padding, jobject bitmapFactoryOptions, jlong inBitmapHandle, jlong colorSpaceHandle) {
-#ifndef __ANDROID__ // LayoutLib for Windows does not support F_DUPFD_CLOEXEC
-      return nullObjectReturn("Not supported on Windows");
+#ifdef _WIN32  // LayoutLib for Windows does not support F_DUPFD_CLOEXEC
+    return nullObjectReturn("Not supported on Windows");
 #else
     NPE_CHECK_RETURN_ZERO(env, fileDescriptor);
 
diff --git a/media/java/android/media/LoudnessCodecDispatcher.java b/media/java/android/media/LoudnessCodecDispatcher.java
index fa08658..bdd3c73 100644
--- a/media/java/android/media/LoudnessCodecDispatcher.java
+++ b/media/java/android/media/LoudnessCodecDispatcher.java
@@ -16,6 +16,9 @@
 
 package android.media;
 
+import static android.media.MediaFormat.KEY_AAC_DRC_ALBUM_MODE;
+import static android.media.MediaFormat.KEY_AAC_DRC_ATTENUATION_FACTOR;
+import static android.media.MediaFormat.KEY_AAC_DRC_BOOST_FACTOR;
 import static android.media.MediaFormat.KEY_AAC_DRC_EFFECT_TYPE;
 import static android.media.MediaFormat.KEY_AAC_DRC_HEAVY_COMPRESSION;
 import static android.media.MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL;
@@ -142,6 +145,18 @@
                 filteredBundle.putInt(KEY_AAC_DRC_EFFECT_TYPE,
                         bundle.getInt(KEY_AAC_DRC_EFFECT_TYPE));
             }
+            if (bundle.containsKey(KEY_AAC_DRC_BOOST_FACTOR)) {
+                filteredBundle.putInt(KEY_AAC_DRC_BOOST_FACTOR,
+                        bundle.getInt(KEY_AAC_DRC_BOOST_FACTOR));
+            }
+            if (bundle.containsKey(KEY_AAC_DRC_ATTENUATION_FACTOR)) {
+                filteredBundle.putInt(KEY_AAC_DRC_ATTENUATION_FACTOR,
+                        bundle.getInt(KEY_AAC_DRC_ATTENUATION_FACTOR));
+            }
+            if (bundle.containsKey(KEY_AAC_DRC_ALBUM_MODE)) {
+                filteredBundle.putInt(KEY_AAC_DRC_ALBUM_MODE,
+                        bundle.getInt(KEY_AAC_DRC_ALBUM_MODE));
+            }
 
             return filteredBundle;
         }
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index 223b432c..4059291 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -109,7 +109,7 @@
         try {
             final Callback c = Objects.requireNonNull(callback);
             if (handler == null) {
-                handler = new Handler();
+                handler = new Handler(mContext.getMainLooper());
             }
             mCallbacks.put(c, new CallbackRecord(c, handler));
         } catch (NullPointerException e) {
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index a488756..70462ef 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -614,12 +614,11 @@
         }
 
         /**
-         * Override to handle changes to the audio info.
+         * Signals a change in the session's {@link PlaybackInfo PlaybackInfo}.
          *
-         * @param info The current audio info for this session.
+         * @param playbackInfo The latest known state of the session's playback info.
          */
-        public void onAudioInfoChanged(PlaybackInfo info) {
-        }
+        public void onAudioInfoChanged(@NonNull PlaybackInfo playbackInfo) {}
     }
 
     /**
@@ -1182,7 +1181,7 @@
         }
 
         @Override
-        public void onVolumeInfoChanged(PlaybackInfo info) {
+        public void onVolumeInfoChanged(@NonNull PlaybackInfo info) {
             MediaController controller = mController.get();
             if (controller != null) {
                 controller.postMessage(MSG_UPDATE_VOLUME, info, null);
diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl
index 7cd7e7ab..7150b54 100644
--- a/nfc/java/android/nfc/INfcAdapter.aidl
+++ b/nfc/java/android/nfc/INfcAdapter.aidl
@@ -91,7 +91,7 @@
     boolean enableReaderOption(boolean enable);
     boolean isObserveModeSupported();
     boolean isObserveModeEnabled();
-    boolean setObserveMode(boolean enabled);
+    boolean setObserveMode(boolean enabled, String pkg);
 
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)")
     boolean setWlcEnabled(boolean enable);
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index 06098de..698df28 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -1268,8 +1268,12 @@
 
     @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
     public boolean setObserveModeEnabled(boolean enabled) {
+        if (mContext == null) {
+            throw new UnsupportedOperationException("You need a context on NfcAdapter to use the "
+                    + " observe mode APIs");
+        }
         try {
-            return sService.setObserveMode(enabled);
+            return sService.setObserveMode(enabled, mContext.getPackageName());
         } catch (RemoteException e) {
             attemptDeadServiceRecovery(e);
             return false;
diff --git a/packages/InputDevices/res/raw/keyboard_layout_french_ca.kcm b/packages/InputDevices/res/raw/keyboard_layout_french_ca.kcm
index 03b5c19..723c187 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_french_ca.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_french_ca.kcm
@@ -348,13 +348,13 @@
     label:                              ','
     base:                               ','
     shift:                              '\''
-    ralt:                               '_'
+    ralt:                               '\u00af'
 }
 
 key PERIOD {
     label:                              '.'
     base:                               '.'
-    ralt:                               '-'
+    ralt:                               '\u00ad'
 }
 
 key SLASH {
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml
index cdd5c25..6052be3 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml
@@ -20,7 +20,7 @@
         <item name="android:colorAccent">@color/settingslib_materialColorPrimary</item>
         <item name="android:colorBackground">@color/settingslib_materialColorSurfaceContainer</item>
         <item name="android:textColorPrimary">@color/settingslib_materialColorOnSurface</item>
-        <item name="android:textColorSecondary">@color/settingslib_materialColorOnSurfaceVariant</item>
+        <item name="android:textColorSecondary">@color/settingslib_text_color_secondary</item>
         <item name="android:textColorTertiary">@color/settingslib_materialColorOutline</item>
     </style>
 
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionController.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionController.kt
new file mode 100644
index 0000000..9350f98
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionController.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.model.app
+
+import android.app.AppOpsManager
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.util.Log
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+
+interface IAppOpsPermissionController {
+    val isAllowedFlow: Flow<Boolean>
+    fun setAllowed(allowed: Boolean)
+}
+
+class AppOpsPermissionController(
+    context: Context,
+    private val app: ApplicationInfo,
+    appOps: AppOps,
+    private val permission: String,
+    private val packageManagers: IPackageManagers = PackageManagers,
+    private val appOpsController: IAppOpsController = AppOpsController(context, app, appOps),
+) : IAppOpsPermissionController {
+    override val isAllowedFlow: Flow<Boolean> = appOpsController.modeFlow.map { mode ->
+        when (mode) {
+            AppOpsManager.MODE_ALLOWED -> true
+
+            AppOpsManager.MODE_DEFAULT -> {
+                with(packageManagers) { app.hasGrantPermission(permission) }
+            }
+
+            else -> false
+        }
+    }.conflate().onEach { Log.d(TAG, "isAllowed: $it") }.flowOn(Dispatchers.Default)
+
+    override fun setAllowed(allowed: Boolean) {
+        appOpsController.setAllowed(allowed)
+    }
+
+    private companion object {
+        private const val TAG = "AppOpsPermissionControl"
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
index 37b1d73..120b75e 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
@@ -20,13 +20,14 @@
 import android.content.Context
 import android.content.pm.ApplicationInfo
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.settingslib.spa.framework.util.asyncMapItem
 import com.android.settingslib.spa.framework.util.filterItem
 import com.android.settingslib.spaprivileged.model.app.AppOps
-import com.android.settingslib.spaprivileged.model.app.AppOpsController
+import com.android.settingslib.spaprivileged.model.app.AppOpsPermissionController
 import com.android.settingslib.spaprivileged.model.app.AppRecord
-import com.android.settingslib.spaprivileged.model.app.IAppOpsController
+import com.android.settingslib.spaprivileged.model.app.IAppOpsPermissionController
 import com.android.settingslib.spaprivileged.model.app.IPackageManagers
 import com.android.settingslib.spaprivileged.model.app.PackageManagers
 import kotlinx.coroutines.flow.Flow
@@ -37,7 +38,7 @@
     override val app: ApplicationInfo,
     val hasRequestBroaderPermission: Boolean,
     val hasRequestPermission: Boolean,
-    var appOpsController: IAppOpsController,
+    var appOpsPermissionController: IAppOpsPermissionController,
 ) : AppRecord
 
 abstract class AppOpPermissionListModel(
@@ -70,8 +71,8 @@
     private val notChangeablePackages =
         setOf("android", "com.android.systemui", context.packageName)
 
-    private fun createAppOpsController(app: ApplicationInfo) =
-        AppOpsController(context, app, appOps)
+    private fun createAppOpsPermissionController(app: ApplicationInfo) =
+        AppOpsPermissionController(context, app, appOps, permission)
 
     private fun createRecord(
         app: ApplicationInfo,
@@ -84,7 +85,7 @@
                     app.hasRequestPermission(it)
                 } ?: false,
                 hasRequestPermission = hasRequestPermission,
-                appOpsController = createAppOpsController(app),
+                appOpsPermissionController = createAppOpsPermissionController(app),
             )
         }
 
@@ -117,14 +118,20 @@
     override fun filter(userIdFlow: Flow<Int>, recordListFlow: Flow<List<AppOpPermissionRecord>>) =
         recordListFlow.filterItem(::isChangeable)
 
+    /**
+     * Defining the default behavior as permissible as long as the package requested this permission
+     * (This means pre-M gets approval during install time; M apps gets approval during runtime).
+     */
     @Composable
-    override fun isAllowed(record: AppOpPermissionRecord): () -> Boolean? =
-        isAllowed(
-            record = record,
-            appOpsController = record.appOpsController,
-            permission = permission,
-            packageManagers = packageManagers,
-        )
+    override fun isAllowed(record: AppOpPermissionRecord): () -> Boolean? {
+        if (record.hasRequestBroaderPermission) {
+            // Broader permission trumps the specific permission.
+            return { true }
+        }
+        val isAllowed by record.appOpsPermissionController.isAllowedFlow
+            .collectAsStateWithLifecycle(initialValue = null)
+        return { isAllowed }
+    }
 
     override fun isChangeable(record: AppOpPermissionRecord) =
         record.hasRequestPermission &&
@@ -132,36 +139,6 @@
             record.app.packageName !in notChangeablePackages
 
     override fun setAllowed(record: AppOpPermissionRecord, newAllowed: Boolean) {
-        record.appOpsController.setAllowed(newAllowed)
-    }
-}
-
-/**
- * Defining the default behavior as permissible as long as the package requested this permission
- * (This means pre-M gets approval during install time; M apps gets approval during runtime).
- */
-@Composable
-internal fun isAllowed(
-    record: AppOpPermissionRecord,
-    appOpsController: IAppOpsController,
-    permission: String,
-    packageManagers: IPackageManagers = PackageManagers,
-): () -> Boolean? {
-    if (record.hasRequestBroaderPermission) {
-        // Broader permission trumps the specific permission.
-        return { true }
-    }
-
-    val mode = appOpsController.modeFlow.collectAsStateWithLifecycle(initialValue = null)
-    return {
-        when (mode.value) {
-            null -> null
-            AppOpsManager.MODE_ALLOWED -> true
-            AppOpsManager.MODE_DEFAULT -> {
-                with(packageManagers) { record.app.hasGrantPermission(permission) }
-            }
-
-            else -> false
-        }
+        record.appOpsPermissionController.setAllowed(newAllowed)
     }
 }
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionControllerTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionControllerTest.kt
new file mode 100644
index 0000000..9f80b92
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionControllerTest.kt
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.model.app
+
+import android.app.AppOpsManager
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
+import com.android.settingslib.spaprivileged.framework.common.appOpsManager
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.stub
+import org.mockito.kotlin.verify
+
+@RunWith(AndroidJUnit4::class)
+class AppOpsPermissionControllerTest {
+
+    private val appOpsManager = mock<AppOpsManager>()
+    private val packageManager = mock<PackageManager>()
+    private val packageManagers = mock<IPackageManagers>()
+    private val appOpsController = mock<IAppOpsController>()
+
+    private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
+        on { appOpsManager } doReturn appOpsManager
+        on { packageManager } doReturn packageManager
+    }
+
+    @Test
+    fun isAllowedFlow_appOpsAllowed_returnTrue() = runBlocking {
+        appOpsController.stub {
+            on { modeFlow } doReturn flowOf(AppOpsManager.MODE_ALLOWED)
+        }
+        val controller = AppOpsPermissionController(
+            context = context,
+            app = APP,
+            appOps = AppOps(op = OP),
+            permission = PERMISSION,
+            appOpsController = appOpsController,
+        )
+
+        val isAllowed = controller.isAllowedFlow.firstWithTimeoutOrNull()
+
+        assertThat(isAllowed).isTrue()
+    }
+
+    @Test
+    fun isAllowedFlow_appOpsDefaultAndPermissionGranted_returnTrue() = runBlocking {
+        appOpsController.stub {
+            on { modeFlow } doReturn flowOf(AppOpsManager.MODE_DEFAULT)
+        }
+        packageManagers.stub {
+            on { APP.hasGrantPermission(PERMISSION) } doReturn true
+        }
+        val controller = AppOpsPermissionController(
+            context = context,
+            app = APP,
+            appOps = AppOps(op = OP),
+            permission = PERMISSION,
+            packageManagers = packageManagers,
+            appOpsController = appOpsController,
+        )
+
+        val isAllowed = controller.isAllowedFlow.firstWithTimeoutOrNull()
+
+        assertThat(isAllowed).isTrue()
+    }
+
+    @Test
+    fun isAllowedFlow_appOpsDefaultAndPermissionNotGranted_returnFalse() = runBlocking {
+        appOpsController.stub {
+            on { modeFlow } doReturn flowOf(AppOpsManager.MODE_DEFAULT)
+        }
+        packageManagers.stub {
+            on { APP.hasGrantPermission(PERMISSION) } doReturn false
+        }
+        val controller = AppOpsPermissionController(
+            context = context,
+            app = APP,
+            appOps = AppOps(op = OP),
+            permission = PERMISSION,
+            packageManagers = packageManagers,
+            appOpsController = appOpsController,
+        )
+
+        val isAllowed = controller.isAllowedFlow.firstWithTimeoutOrNull()
+
+        assertThat(isAllowed).isFalse()
+    }
+
+    @Test
+    fun isAllowedFlow_appOpsError_returnFalse() = runBlocking {
+        appOpsController.stub {
+            on { modeFlow } doReturn flowOf(AppOpsManager.MODE_ERRORED)
+        }
+        val controller = AppOpsPermissionController(
+            context = context,
+            app = APP,
+            appOps = AppOps(op = OP),
+            permission = PERMISSION,
+            appOpsController = appOpsController,
+        )
+
+        val isAllowed = controller.isAllowedFlow.firstWithTimeoutOrNull()
+
+        assertThat(isAllowed).isFalse()
+    }
+
+    @Test
+    fun setAllowed_notSetModeByUid() {
+        val controller = AppOpsPermissionController(
+            context = context,
+            app = APP,
+            appOps = AppOps(op = OP, setModeByUid = false),
+            permission = PERMISSION,
+        )
+
+        controller.setAllowed(true)
+
+        verify(appOpsManager).setMode(OP, APP.uid, APP.packageName, AppOpsManager.MODE_ALLOWED)
+    }
+
+    @Test
+    fun setAllowed_setModeByUid() {
+        val controller = AppOpsPermissionController(
+            context = context,
+            app = APP,
+            appOps = AppOps(op = OP, setModeByUid = true),
+            permission = PERMISSION,
+        )
+
+        controller.setAllowed(true)
+
+        verify(appOpsManager).setUidMode(OP, APP.uid, AppOpsManager.MODE_ALLOWED)
+    }
+
+    private companion object {
+        const val OP = 1
+        const val PERMISSION = "Permission"
+        val APP = ApplicationInfo().apply {
+            packageName = "package.name"
+            uid = 123
+        }
+    }
+}
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 07ccdd5..9d12fc7 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
@@ -26,7 +26,7 @@
 import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
 import com.android.settingslib.spaprivileged.framework.common.appOpsManager
 import com.android.settingslib.spaprivileged.model.app.AppOps
-import com.android.settingslib.spaprivileged.model.app.IAppOpsController
+import com.android.settingslib.spaprivileged.model.app.IAppOpsPermissionController
 import com.android.settingslib.spaprivileged.model.app.IPackageManagers
 import com.android.settingslib.spaprivileged.test.R
 import com.google.common.truth.Truth.assertThat
@@ -119,7 +119,7 @@
                 app = APP,
                 hasRequestBroaderPermission = false,
                 hasRequestPermission = false,
-                appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+                appOpsPermissionController = FakeAppOpsPermissionController(false),
             )
 
         val recordListFlow = listModel.filter(flowOf(USER_ID), flowOf(listOf(record)))
@@ -135,7 +135,7 @@
                 app = APP,
                 hasRequestBroaderPermission = false,
                 hasRequestPermission = true,
-                appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_ALLOWED),
+                appOpsPermissionController = FakeAppOpsPermissionController(true),
             )
 
         val isAllowed = getIsAllowed(record)
@@ -144,38 +144,6 @@
     }
 
     @Test
-    fun isAllowed_defaultAndHasGrantPermission() {
-        with(packageManagers) { whenever(APP.hasGrantPermission(PERMISSION)).thenReturn(true) }
-        val record =
-            AppOpPermissionRecord(
-                app = APP,
-                hasRequestBroaderPermission = false,
-                hasRequestPermission = true,
-                appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
-            )
-
-        val isAllowed = getIsAllowed(record)
-
-        assertThat(isAllowed).isTrue()
-    }
-
-    @Test
-    fun isAllowed_defaultAndNotGrantPermission() {
-        with(packageManagers) { whenever(APP.hasGrantPermission(PERMISSION)).thenReturn(false) }
-        val record =
-            AppOpPermissionRecord(
-                app = APP,
-                hasRequestBroaderPermission = false,
-                hasRequestPermission = true,
-                appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
-            )
-
-        val isAllowed = getIsAllowed(record)
-
-        assertThat(isAllowed).isFalse()
-    }
-
-    @Test
     fun isAllowed_broaderPermissionTrumps() {
         listModel.broaderPermission = BROADER_PERMISSION
         with(packageManagers) {
@@ -187,7 +155,7 @@
                 app = APP,
                 hasRequestBroaderPermission = true,
                 hasRequestPermission = false,
-                appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_ERRORED),
+                appOpsPermissionController = FakeAppOpsPermissionController(false),
             )
 
         val isAllowed = getIsAllowed(record)
@@ -202,7 +170,7 @@
                 app = APP,
                 hasRequestBroaderPermission = false,
                 hasRequestPermission = true,
-                appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_ERRORED),
+                appOpsPermissionController = FakeAppOpsPermissionController(false),
             )
 
         val isAllowed = getIsAllowed(record)
@@ -217,7 +185,7 @@
                 app = APP,
                 hasRequestBroaderPermission = false,
                 hasRequestPermission = false,
-                appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+                appOpsPermissionController = FakeAppOpsPermissionController(false),
             )
 
         val isChangeable = listModel.isChangeable(record)
@@ -232,7 +200,7 @@
                 app = NOT_CHANGEABLE_APP,
                 hasRequestBroaderPermission = false,
                 hasRequestPermission = true,
-                appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+                appOpsPermissionController = FakeAppOpsPermissionController(false),
             )
 
         val isChangeable = listModel.isChangeable(record)
@@ -247,7 +215,7 @@
                 app = APP,
                 hasRequestBroaderPermission = false,
                 hasRequestPermission = true,
-                appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+                appOpsPermissionController = FakeAppOpsPermissionController(false),
             )
 
         val isChangeable = listModel.isChangeable(record)
@@ -263,7 +231,7 @@
                 app = APP,
                 hasRequestBroaderPermission = true,
                 hasRequestPermission = true,
-                appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+                appOpsPermissionController = FakeAppOpsPermissionController(false),
             )
 
         val isChangeable = listModel.isChangeable(record)
@@ -273,18 +241,18 @@
 
     @Test
     fun setAllowed() {
-        val appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT)
+        val appOpsPermissionController = FakeAppOpsPermissionController(false)
         val record =
             AppOpPermissionRecord(
                 app = APP,
                 hasRequestBroaderPermission = false,
                 hasRequestPermission = true,
-                appOpsController = appOpsController,
+                appOpsPermissionController = appOpsPermissionController,
             )
 
         listModel.setAllowed(record = record, newAllowed = true)
 
-        assertThat(appOpsController.setAllowedCalledWith).isTrue()
+        assertThat(appOpsPermissionController.setAllowedCalledWith).isTrue()
     }
 
     private fun getIsAllowed(record: AppOpPermissionRecord): Boolean? {
@@ -314,14 +282,12 @@
     }
 }
 
-private class FakeAppOpsController(private val fakeMode: Int) : IAppOpsController {
+private class FakeAppOpsPermissionController(allowed: Boolean) : IAppOpsPermissionController {
     var setAllowedCalledWith: Boolean? = null
 
-    override val modeFlow = flowOf(fakeMode)
+    override val isAllowedFlow = flowOf(allowed)
 
     override fun setAllowed(allowed: Boolean) {
         setAllowedCalledWith = allowed
     }
-
-    override fun getMode() = fakeMode
 }
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index 9c0d29d..32557b9 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -69,3 +69,13 @@
   description: "Allow all widgets on the lock screen by default."
   bug: "328261690"
 }
+
+flag {
+    name: "enable_determining_advanced_details_header_with_metadata"
+    namespace: "pixel_cross_device_control"
+    description: "Use metadata instead of device type to determine whether a bluetooth device should use advanced details header."
+    bug: "328556903"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig b/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig
index 7aae1a6..6f614b3 100644
--- a/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig
@@ -31,3 +31,14 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "use_playback_info_for_routing_controls"
+    namespace: "media_solutions"
+    description: "Use app-provided playback info when providing media routing information."
+    bug: "333564788"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 8917412..721e7b9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -276,6 +276,14 @@
         if (isUntetheredHeadset(bluetoothDevice)) {
             return true;
         }
+        if (Flags.enableDeterminingAdvancedDetailsHeaderWithMetadata()) {
+            // A FastPair device that use advanced details header must have METADATA_MAIN_ICON
+            if (getUriMetaData(bluetoothDevice, BluetoothDevice.METADATA_MAIN_ICON) != null) {
+                Log.d(TAG, "isAdvancedDetailsHeader is true with main icon uri");
+                return true;
+            }
+            return false;
+        }
         // The metadata is for Android S
         String deviceType =
                 getStringMetaData(bluetoothDevice, BluetoothDevice.METADATA_DEVICE_TYPE);
@@ -302,12 +310,15 @@
         if (isUntetheredHeadset(bluetoothDevice)) {
             return true;
         }
-        // The metadata is for Android S
-        String deviceType =
-                getStringMetaData(bluetoothDevice, BluetoothDevice.METADATA_DEVICE_TYPE);
-        if (TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET)) {
-            Log.d(TAG, "isAdvancedUntetheredDevice: is untethered device ");
-            return true;
+        if (!Flags.enableDeterminingAdvancedDetailsHeaderWithMetadata()) {
+            // The METADATA_IS_UNTETHERED_HEADSET of an untethered FastPair headset is always true,
+            // so there's no need to check the device type.
+            String deviceType =
+                    getStringMetaData(bluetoothDevice, BluetoothDevice.METADATA_DEVICE_TYPE);
+            if (TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET)) {
+                Log.d(TAG, "isAdvancedUntetheredDevice: is untethered device");
+                return true;
+            }
         }
         return false;
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index eae58ad..b7758de 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -40,6 +40,7 @@
 import static android.media.MediaRoute2Info.TYPE_USB_HEADSET;
 import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
 import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
+import static android.media.session.MediaController.PlaybackInfo;
 
 import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_SELECTED;
 
@@ -51,6 +52,8 @@
 import android.media.MediaRoute2Info;
 import android.media.RouteListingPreference;
 import android.media.RoutingSessionInfo;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
 import android.os.Build;
 import android.os.UserHandle;
 import android.text.TextUtils;
@@ -135,19 +138,28 @@
     @NonNull protected final UserHandle mUserHandle;
     private final Collection<MediaDeviceCallback> mCallbacks = new CopyOnWriteArrayList<>();
     private MediaDevice mCurrentConnectedDevice;
+    private MediaController mMediaController;
+    private PlaybackInfo mLastKnownPlaybackInfo;
     private final LocalBluetoothManager mBluetoothManager;
     private final Map<String, RouteListingPreference.Item> mPreferenceItemMap =
             new ConcurrentHashMap<>();
 
+    private final MediaController.Callback mMediaControllerCallback = new MediaControllerCallback();
+
     /* package */ InfoMediaManager(
             @NonNull Context context,
             @NonNull String packageName,
             @NonNull UserHandle userHandle,
-            @NonNull LocalBluetoothManager localBluetoothManager) {
+            @NonNull LocalBluetoothManager localBluetoothManager,
+            @Nullable MediaController mediaController) {
         mContext = context;
         mBluetoothManager = localBluetoothManager;
         mPackageName = packageName;
         mUserHandle = userHandle;
+        mMediaController = mediaController;
+        if (mediaController != null) {
+            mLastKnownPlaybackInfo = mediaController.getPlaybackInfo();
+        }
     }
 
     /**
@@ -159,12 +171,19 @@
      *     speakers, as opposed to app-specific routing (for example, casting to another device).
      * @param userHandle The {@link UserHandle} of the user on which the app to control is running,
      *     or null if the caller does not need app-specific routing (see {@code packageName}).
+     * @param token The token of the associated {@link MediaSession} for which to do media routing.
      */
     public static InfoMediaManager createInstance(
             Context context,
             @Nullable String packageName,
             @Nullable UserHandle userHandle,
-            LocalBluetoothManager localBluetoothManager) {
+            LocalBluetoothManager localBluetoothManager,
+            @Nullable MediaSession.Token token) {
+        MediaController mediaController = null;
+
+        if (Flags.usePlaybackInfoForRoutingControls() && token != null) {
+            mediaController = new MediaController(context, token);
+        }
 
         // The caller is only interested in system routes (headsets, built-in speakers, etc), and is
         // not interested in a specific app's routing. The media routing APIs still require a
@@ -180,16 +199,16 @@
         if (Flags.useMediaRouter2ForInfoMediaManager()) {
             try {
                 return new RouterInfoMediaManager(
-                        context, packageName, userHandle, localBluetoothManager);
+                        context, packageName, userHandle, localBluetoothManager, mediaController);
             } catch (PackageNotAvailableException ex) {
                 // TODO: b/293578081 - Propagate this exception to callers for proper handling.
                 Log.w(TAG, "Returning a no-op InfoMediaManager for package " + packageName);
                 return new NoOpInfoMediaManager(
-                        context, packageName, userHandle, localBluetoothManager);
+                        context, packageName, userHandle, localBluetoothManager, mediaController);
             }
         } else {
             return new ManagerInfoMediaManager(
-                    context, packageName, userHandle, localBluetoothManager);
+                    context, packageName, userHandle, localBluetoothManager, mediaController);
         }
     }
 
@@ -310,6 +329,9 @@
             if (wasEmpty) {
                 mMediaDevices.clear();
                 registerRouter();
+                if (mMediaController != null) {
+                    mMediaController.registerCallback(mMediaControllerCallback);
+                }
                 updateRouteListingPreference();
                 refreshDevices();
             }
@@ -323,6 +345,9 @@
      */
     public final void unregisterCallback(@NonNull MediaDeviceCallback callback) {
         if (mCallbacks.remove(callback) && mCallbacks.isEmpty()) {
+            if (mMediaController != null) {
+                mMediaController.unregisterCallback(mMediaControllerCallback);
+            }
             unregisterRouter();
         }
     }
@@ -389,7 +414,34 @@
     private RoutingSessionInfo getActiveRoutingSession() {
         // List is never empty.
         final List<RoutingSessionInfo> sessions = getRoutingSessionsForPackage();
-        return sessions.get(sessions.size() - 1);
+        RoutingSessionInfo activeSession = sessions.get(sessions.size() - 1);
+
+        // Logic from MediaRouter2Manager#getRoutingSessionForMediaController
+        if (!Flags.usePlaybackInfoForRoutingControls() || mMediaController == null) {
+            return activeSession;
+        }
+
+        PlaybackInfo playbackInfo = mMediaController.getPlaybackInfo();
+        if (playbackInfo.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_LOCAL) {
+            // Return system session.
+            return sessions.get(0);
+        }
+
+        // For PLAYBACK_TYPE_REMOTE.
+        String volumeControlId = playbackInfo.getVolumeControlId();
+        for (RoutingSessionInfo session : sessions) {
+            if (TextUtils.equals(volumeControlId, session.getId())) {
+                return session;
+            }
+            // Workaround for provider not being able to know the unique session ID.
+            if (TextUtils.equals(volumeControlId, session.getOriginalId())
+                    && TextUtils.equals(
+                            mMediaController.getPackageName(), session.getOwnerPackageName())) {
+                return session;
+            }
+        }
+
+        return activeSession;
     }
 
     boolean isRoutingSessionAvailableForVolumeControl() {
@@ -808,4 +860,23 @@
             }
         }
     }
+
+    private final class MediaControllerCallback extends MediaController.Callback {
+        @Override
+        public void onSessionDestroyed() {
+            mMediaController = null;
+            refreshDevices();
+        }
+
+        @Override
+        public void onAudioInfoChanged(@NonNull PlaybackInfo info) {
+            if (info.getPlaybackType() != mLastKnownPlaybackInfo.getPlaybackType()
+                    || !TextUtils.equals(
+                            info.getVolumeControlId(),
+                            mLastKnownPlaybackInfo.getVolumeControlId())) {
+                refreshDevices();
+            }
+            mLastKnownPlaybackInfo = info;
+        }
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 473c627..cfa825b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -149,7 +149,11 @@
                 // TODO: b/321969740 - Take the userHandle as a parameter and pass it through. The
                 // package name is not sufficient to unambiguously identify an app.
                 InfoMediaManager.createInstance(
-                        context, packageName, /* userHandle */ null, mLocalBluetoothManager);
+                        context,
+                        packageName,
+                        /* userHandle */ null,
+                        mLocalBluetoothManager,
+                        /* token */ null);
     }
 
     /**
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
index d621751..82b1976 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
@@ -21,6 +21,7 @@
 import android.media.MediaRouter2Manager;
 import android.media.RouteListingPreference;
 import android.media.RoutingSessionInfo;
+import android.media.session.MediaController;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
@@ -55,8 +56,9 @@
             Context context,
             @NonNull String packageName,
             @NonNull UserHandle userHandle,
-            LocalBluetoothManager localBluetoothManager) {
-        super(context, packageName, userHandle, localBluetoothManager);
+            LocalBluetoothManager localBluetoothManager,
+            @Nullable MediaController mediaController) {
+        super(context, packageName, userHandle, localBluetoothManager, mediaController);
 
         mRouterManager = MediaRouter2Manager.getInstance(context);
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
index d2b018c..2c7ec93 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
@@ -20,6 +20,7 @@
 import android.media.MediaRoute2Info;
 import android.media.RouteListingPreference;
 import android.media.RoutingSessionInfo;
+import android.media.session.MediaController;
 import android.os.UserHandle;
 
 import androidx.annotation.NonNull;
@@ -60,8 +61,9 @@
             Context context,
             @NonNull String packageName,
             @NonNull UserHandle userHandle,
-            LocalBluetoothManager localBluetoothManager) {
-        super(context, packageName, userHandle, localBluetoothManager);
+            LocalBluetoothManager localBluetoothManager,
+            @Nullable MediaController mediaController) {
+        super(context, packageName, userHandle, localBluetoothManager, mediaController);
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
index 045c60d..6571dd7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
@@ -25,6 +25,7 @@
 import android.media.RouteDiscoveryPreference;
 import android.media.RouteListingPreference;
 import android.media.RoutingSessionInfo;
+import android.media.session.MediaController;
 import android.os.UserHandle;
 import android.text.TextUtils;
 
@@ -71,9 +72,10 @@
             Context context,
             @NonNull String packageName,
             @NonNull UserHandle userHandle,
-            LocalBluetoothManager localBluetoothManager)
+            LocalBluetoothManager localBluetoothManager,
+            @Nullable MediaController mediaController)
             throws PackageNotAvailableException {
-        super(context, packageName, userHandle, localBluetoothManager);
+        super(context, packageName, userHandle, localBluetoothManager, mediaController);
 
         MediaRouter2 router = null;
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.java b/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.java
index 23b2cc2..89f3cf5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.java
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.java
@@ -278,7 +278,7 @@
         }
 
         @Override
-        public void onAudioInfoChanged(PlaybackInfo info) {
+        public void onAudioInfoChanged(@NonNull PlaybackInfo info) {
             if (D.BUG) {
                 Log.d(TAG, cb("onAudioInfoChanged") + Util.playbackInfoToString(info)
                         + " sentRemote=" + sentRemote);
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java
index 3bd37a2..a2ee2ec 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java
@@ -65,7 +65,7 @@
     public void createInstance_withMR2FlagOn_returnsRouterInfoMediaManager() {
         InfoMediaManager manager =
                 InfoMediaManager.createInstance(
-                        mContext, mContext.getPackageName(), mContext.getUser(), null);
+                        mContext, mContext.getPackageName(), mContext.getUser(), null, null);
         assertThat(manager).isInstanceOf(RouterInfoMediaManager.class);
     }
 
@@ -73,14 +73,15 @@
     @RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
     public void createInstance_withMR2FlagOn_withFakePackage_returnsNoOpInfoMediaManager() {
         InfoMediaManager manager =
-                InfoMediaManager.createInstance(mContext, FAKE_PACKAGE, null, null);
+                InfoMediaManager.createInstance(mContext, FAKE_PACKAGE, null, null, null);
         assertThat(manager).isInstanceOf(NoOpInfoMediaManager.class);
     }
 
     @Test
     @RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
     public void createInstance_withMR2FlagOn_withNullPackage_returnsRouterInfoMediaManager() {
-        InfoMediaManager manager = InfoMediaManager.createInstance(mContext, null, null, null);
+        InfoMediaManager manager =
+                InfoMediaManager.createInstance(mContext, null, null, null, null);
         assertThat(manager).isInstanceOf(RouterInfoMediaManager.class);
     }
 
@@ -89,7 +90,7 @@
     public void createInstance_withMR2FlagOff_returnsManagerInfoMediaManager() {
         InfoMediaManager manager =
                 InfoMediaManager.createInstance(
-                        mContext, mContext.getPackageName(), mContext.getUser(), null);
+                        mContext, mContext.getPackageName(), mContext.getUser(), null, null);
         assertThat(manager).isInstanceOf(ManagerInfoMediaManager.class);
     }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index 7a2818d..a638df5 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -15,6 +15,8 @@
  */
 package com.android.settingslib.bluetooth;
 
+import static com.android.settingslib.flags.Flags.FLAG_ENABLE_DETERMINING_ADVANCED_DETAILS_HEADER_WITH_METADATA;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -33,11 +35,13 @@
 import android.graphics.drawable.Drawable;
 import android.media.AudioManager;
 import android.net.Uri;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.Pair;
 
 import com.android.settingslib.widget.AdaptiveIcon;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Answers;
@@ -83,11 +87,15 @@
     private static final String TEST_EXCLUSIVE_MANAGER_PACKAGE = "com.test.manager";
     private static final String TEST_EXCLUSIVE_MANAGER_COMPONENT = "com.test.manager/.component";
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
         mContext = spy(RuntimeEnvironment.application);
+        mSetFlagsRule.disableFlags(FLAG_ENABLE_DETERMINING_ADVANCED_DETAILS_HEADER_WITH_METADATA);
         when(mLocalBluetoothManager.getProfileManager()).thenReturn(mProfileManager);
         when(mProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast);
         when(mProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(mAssistant);
@@ -253,6 +261,25 @@
     }
 
     @Test
+    public void isAdvancedDetailsHeader_noMainIcon_returnFalse() {
+        mSetFlagsRule.enableFlags(FLAG_ENABLE_DETERMINING_ADVANCED_DETAILS_HEADER_WITH_METADATA);
+
+        when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_MAIN_ICON)).thenReturn(null);
+
+        assertThat(BluetoothUtils.isAdvancedDetailsHeader(mBluetoothDevice)).isFalse();
+    }
+
+    @Test
+    public void isAdvancedDetailsHeader_hasMainIcon_returnTrue() {
+        mSetFlagsRule.enableFlags(FLAG_ENABLE_DETERMINING_ADVANCED_DETAILS_HEADER_WITH_METADATA);
+
+        when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_MAIN_ICON))
+                .thenReturn(STRING_METADATA.getBytes());
+
+        assertThat(BluetoothUtils.isAdvancedDetailsHeader(mBluetoothDevice)).isTrue();
+    }
+
+    @Test
     public void isAdvancedUntetheredDevice_untetheredHeadset_returnTrue() {
         when(mBluetoothDevice.getMetadata(
                 BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
@@ -294,6 +321,18 @@
     }
 
     @Test
+    public void isAdvancedUntetheredDevice_untetheredHeadsetMetadataIsFalse_returnFalse() {
+        mSetFlagsRule.enableFlags(FLAG_ENABLE_DETERMINING_ADVANCED_DETAILS_HEADER_WITH_METADATA);
+
+        when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET))
+                .thenReturn("false".getBytes());
+        when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_DEVICE_TYPE))
+                .thenReturn(BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET.getBytes());
+
+        assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isFalse();
+    }
+
+    @Test
     public void isAvailableMediaBluetoothDevice_isConnectedLeAudioDevice_returnTrue() {
         when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
         when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
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 69faddf..ce07fe9 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
@@ -146,7 +146,11 @@
                 Context.MEDIA_SESSION_SERVICE);
         mInfoMediaManager =
                 new ManagerInfoMediaManager(
-                        mContext, TEST_PACKAGE_NAME, mContext.getUser(), mLocalBluetoothManager);
+                        mContext,
+                        TEST_PACKAGE_NAME,
+                        mContext.getUser(),
+                        mLocalBluetoothManager,
+                        /* mediaController */ null);
         mShadowRouter2Manager = ShadowRouter2Manager.getShadow();
         mInfoMediaManager.mRouterManager = MediaRouter2Manager.getInstance(mContext);
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index ddb5419..12541bb 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -117,9 +117,16 @@
         when(mLocalProfileManager.getHearingAidProfile()).thenReturn(mHapProfile);
 
         // Need to call constructor to initialize final fields.
-        mInfoMediaManager = mock(
-                InfoMediaManager.class,
-                withSettings().useConstructor(mContext, TEST_PACKAGE_NAME, mLocalBluetoothManager));
+        mInfoMediaManager =
+                mock(
+                        InfoMediaManager.class,
+                        withSettings()
+                                .useConstructor(
+                                        mContext,
+                                        TEST_PACKAGE_NAME,
+                                        android.os.Process.myUserHandle(),
+                                        mLocalBluetoothManager,
+                                        /* mediaController */ null));
         doReturn(
                         List.of(
                                 new RoutingSessionInfo.Builder(TEST_SESSION_ID, TEST_PACKAGE_NAME)
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/NoOpInfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/NoOpInfoMediaManagerTest.java
index 908f50d..c566741 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/NoOpInfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/NoOpInfoMediaManagerTest.java
@@ -47,7 +47,8 @@
                         mContext,
                         /* packageName */ "FAKE_PACKAGE_NAME",
                         mContext.getUser(),
-                        /* localBluetoothManager */ null);
+                        /* localBluetoothManager */ null,
+                        /* mediaController */ null);
     }
 
     @Test
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index 8f8445d..63e98de 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -38,6 +38,8 @@
      * NOTE: All settings which are backed up should have a corresponding validator.
      */
     public static final String[] SETTINGS_TO_BACKUP = {
+        Settings.Global.CONNECTED_APPS_ALLOWED_PACKAGES,
+        Settings.Global.CONNECTED_APPS_DISALLOWED_PACKAGES,
         Settings.Global.APPLY_RAMPING_RINGER,
         Settings.Global.BUGREPORT_IN_POWER_MENU,                        // moved to secure
         Settings.Global.STAY_ON_WHILE_PLUGGED_IN,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index c274534..bc0ee7f 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -51,6 +51,9 @@
     public static final Map<String, Validator> VALIDATORS = new ArrayMap<>();
 
     static {
+        VALIDATORS.put(Global.CONNECTED_APPS_ALLOWED_PACKAGES, new PackageNameListValidator((",")));
+        VALIDATORS.put(Global.CONNECTED_APPS_DISALLOWED_PACKAGES,
+                new PackageNameListValidator((",")));
         VALIDATORS.put(Global.APPLY_RAMPING_RINGER, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.BUGREPORT_IN_POWER_MENU, BOOLEAN_VALIDATOR);
         VALIDATORS.put(
diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
index 4579168..050a370 100644
--- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
+++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
@@ -200,7 +200,7 @@
             mBugreportFd = ParcelFileDescriptor.dup(invocation.getArgument(2));
             return null;
         }).when(mMockIDumpstate).startBugreport(anyInt(), any(), any(), any(), anyInt(), anyInt(),
-                any(), anyBoolean());
+                any(), anyBoolean(), anyBoolean());
 
         setWarningState(mContext, STATE_HIDE);
 
@@ -543,7 +543,7 @@
         getInstrumentation().waitForIdleSync();
 
         verify(mMockIDumpstate, times(1)).startBugreport(anyInt(), any(), any(), any(),
-                anyInt(), anyInt(), any(), anyBoolean());
+                anyInt(), anyInt(), any(), anyBoolean(), anyBoolean());
         sendBugreportFinished();
     }
 
@@ -608,7 +608,7 @@
         ArgumentCaptor<IDumpstateListener> listenerCap = ArgumentCaptor.forClass(
                 IDumpstateListener.class);
         verify(mMockIDumpstate, timeout(TIMEOUT)).startBugreport(anyInt(), any(), any(), any(),
-                anyInt(), anyInt(), listenerCap.capture(), anyBoolean());
+                anyInt(), anyInt(), listenerCap.capture(), anyBoolean(), anyBoolean());
         mIDumpstateListener = listenerCap.getValue();
         assertNotNull("Dumpstate listener should not be null", mIDumpstateListener);
         mIDumpstateListener.onProgress(0);
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 8b60ed0..c4929a1 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -766,6 +766,7 @@
     ],
     static_libs: [
         "RoboTestLibraries",
+        "mockito-kotlin2",
     ],
     libs: [
         "android.test.runner",
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
index 15df1be..76ffc8b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
@@ -68,13 +68,17 @@
     @Composable
     private fun Content(dialog: SystemUIDialog) {
         val isAvailable by viewModel.isAvailable.collectAsStateWithLifecycle(true)
-
         if (!isAvailable) {
             SideEffect { dialog.dismiss() }
             return
         }
 
         val slice by viewModel.popupSlice.collectAsStateWithLifecycle()
+        if (!viewModel.isClickable(slice)) {
+            SideEffect { dialog.dismiss() }
+            return
+        }
+
         SliceAndroidView(
             modifier = Modifier.fillMaxWidth(),
             slice = slice,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
similarity index 62%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
index f61ddeb..68fbd1c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
@@ -20,9 +20,15 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.accessibility.data.repository.fakeAccessibilityRepository
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.AuthenticationResult
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
 import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
 import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
@@ -30,14 +36,16 @@
 import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
 import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel.Companion.UNLOCKED_DELAY_MS
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
-import kotlin.test.Test
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
+import org.junit.Test
 import org.junit.runner.RunWith
 
 @ExperimentalCoroutinesApi
@@ -65,9 +73,10 @@
     fun isLongPressEnabled_udfpsRunning() =
         testScope.runTest {
             val isLongPressEnabled by collectLastValue(underTest.isLongPressEnabled)
-            fingerprintPropertyRepository.supportsUdfps()
-            fingerprintAuthRepository.setIsRunning(true)
-            keyguardRepository.setKeyguardDismissible(false)
+            setUpState(
+                isUdfpsSupported = true,
+                isUdfpsRunning = true,
+            )
             assertThat(isLongPressEnabled).isFalse()
         }
 
@@ -75,10 +84,10 @@
     fun isLongPressEnabled_unlocked() =
         testScope.runTest {
             val isLongPressEnabled by collectLastValue(underTest.isLongPressEnabled)
-            fingerprintPropertyRepository.supportsUdfps()
-            keyguardRepository.setKeyguardDismissible(true)
-            advanceTimeBy(UNLOCKED_DELAY_MS * 2) // wait for unlocked delay
-            runCurrent()
+            setUpState(
+                isUdfpsSupported = true,
+                isLockscreenDismissible = true,
+            )
             assertThat(isLongPressEnabled).isTrue()
         }
 
@@ -86,10 +95,9 @@
     fun isLongPressEnabled_lock() =
         testScope.runTest {
             val isLongPressEnabled by collectLastValue(underTest.isLongPressEnabled)
-            keyguardRepository.setKeyguardDismissible(false)
+            setUpState(isUdfpsSupported = true)
 
             // udfps supported
-            fingerprintPropertyRepository.supportsUdfps()
             assertThat(isLongPressEnabled).isTrue()
 
             // udfps isn't supported
@@ -112,42 +120,90 @@
         }
 
     @Test
+    @DisableSceneContainer
     fun iconType_fingerprint() =
         testScope.runTest {
             val iconType by collectLastValue(underTest.iconType)
-            keyguardRepository.setKeyguardDismissible(false)
-            fingerprintPropertyRepository.supportsUdfps()
-            fingerprintAuthRepository.setIsRunning(true)
+            setUpState(
+                isUdfpsSupported = true,
+                isUdfpsRunning = true,
+            )
             assertThat(iconType).isEqualTo(DeviceEntryIconView.IconType.FINGERPRINT)
         }
 
     @Test
+    @DisableSceneContainer
     fun iconType_locked() =
         testScope.runTest {
             val iconType by collectLastValue(underTest.iconType)
-            keyguardRepository.setKeyguardDismissible(false)
-            fingerprintAuthRepository.setIsRunning(false)
+            setUpState()
             assertThat(iconType).isEqualTo(DeviceEntryIconView.IconType.LOCK)
         }
 
     @Test
+    @DisableSceneContainer
     fun iconType_unlocked() =
         testScope.runTest {
             val iconType by collectLastValue(underTest.iconType)
-            keyguardRepository.setKeyguardDismissible(true)
-            advanceTimeBy(UNLOCKED_DELAY_MS * 2) // wait for unlocked delay
-            fingerprintAuthRepository.setIsRunning(false)
+            setUpState(isLockscreenDismissible = true)
             assertThat(iconType).isEqualTo(DeviceEntryIconView.IconType.UNLOCK)
         }
 
     @Test
+    @DisableSceneContainer
     fun iconType_none() =
         testScope.runTest {
             val iconType by collectLastValue(underTest.iconType)
-            keyguardRepository.setKeyguardDismissible(true)
-            advanceTimeBy(UNLOCKED_DELAY_MS * 2) // wait for unlocked delay
-            fingerprintPropertyRepository.supportsUdfps()
-            fingerprintAuthRepository.setIsRunning(true)
+            setUpState(
+                isUdfpsSupported = true,
+                isUdfpsRunning = true,
+                isLockscreenDismissible = true,
+            )
+            assertThat(iconType).isEqualTo(DeviceEntryIconView.IconType.NONE)
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun iconType_fingerprint_withSceneContainer() =
+        testScope.runTest {
+            val iconType by collectLastValue(underTest.iconType)
+            setUpState(
+                isUdfpsSupported = true,
+                isUdfpsRunning = true,
+            )
+            assertThat(iconType).isEqualTo(DeviceEntryIconView.IconType.FINGERPRINT)
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun iconType_locked_withSceneContainer() =
+        testScope.runTest {
+            val iconType by collectLastValue(underTest.iconType)
+            setUpState()
+            assertThat(iconType).isEqualTo(DeviceEntryIconView.IconType.LOCK)
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun iconType_unlocked_withSceneContainer() =
+        testScope.runTest {
+            val iconType by collectLastValue(underTest.iconType)
+            setUpState(
+                isLockscreenDismissible = true,
+            )
+            assertThat(iconType).isEqualTo(DeviceEntryIconView.IconType.UNLOCK)
+        }
+
+    @Test
+    @EnableSceneContainer
+    fun iconType_none_withSceneContainer() =
+        testScope.runTest {
+            val iconType by collectLastValue(underTest.iconType)
+            setUpState(
+                isUdfpsSupported = true,
+                isUdfpsRunning = true,
+                isLockscreenDismissible = true,
+            )
             assertThat(iconType).isEqualTo(DeviceEntryIconView.IconType.NONE)
         }
 
@@ -166,14 +222,12 @@
             kosmos.fakeAccessibilityRepository.isEnabled.value = true
 
             // interactive lock icon
-            keyguardRepository.setKeyguardDismissible(false)
-            fingerprintPropertyRepository.supportsUdfps()
+            setUpState(isUdfpsSupported = true)
 
             assertThat(accessibilityDelegateHint)
                 .isEqualTo(DeviceEntryIconView.AccessibilityHintType.AUTHENTICATE)
 
             // non-interactive lock icon
-            keyguardRepository.setKeyguardDismissible(false)
             fingerprintPropertyRepository.supportsRearFps()
 
             assertThat(accessibilityDelegateHint)
@@ -187,10 +241,10 @@
             kosmos.fakeAccessibilityRepository.isEnabled.value = true
 
             // interactive unlock icon
-            keyguardRepository.setKeyguardDismissible(true)
-            fingerprintPropertyRepository.supportsUdfps()
-            advanceTimeBy(UNLOCKED_DELAY_MS * 2) // wait for unlocked delay
-            runCurrent()
+            setUpState(
+                isUdfpsSupported = true,
+                isLockscreenDismissible = true,
+            )
 
             assertThat(accessibilityDelegateHint)
                 .isEqualTo(DeviceEntryIconView.AccessibilityHintType.ENTER)
@@ -199,4 +253,45 @@
     private fun deviceEntryIconTransitionAlpha(alpha: Float) {
         deviceEntryIconTransition.setDeviceEntryParentViewAlpha(alpha)
     }
+
+    private suspend fun TestScope.setUpState(
+        isUdfpsSupported: Boolean = false,
+        isUdfpsRunning: Boolean = false,
+        isLockscreenDismissible: Boolean = false,
+    ) {
+        if (isUdfpsSupported) {
+            fingerprintPropertyRepository.supportsUdfps()
+        }
+        if (isUdfpsRunning) {
+            check(isUdfpsSupported) { "Cannot set UDFPS as running if it's not supported!" }
+            fingerprintAuthRepository.setIsRunning(true)
+        } else {
+            fingerprintAuthRepository.setIsRunning(false)
+        }
+        if (isLockscreenDismissible) {
+            setLockscreenDismissible()
+        } else {
+            if (!SceneContainerFlag.isEnabled) {
+                keyguardRepository.setKeyguardDismissible(false)
+            }
+        }
+        runCurrent()
+    }
+
+    private suspend fun TestScope.setLockscreenDismissible() {
+        if (SceneContainerFlag.isEnabled) {
+            // Need to set up a collection for the authentication to be propagated.
+            val unused by collectLastValue(kosmos.deviceUnlockedInteractor.deviceUnlockStatus)
+            runCurrent()
+            assertThat(
+                    kosmos.authenticationInteractor.authenticate(
+                        FakeAuthenticationRepository.DEFAULT_PIN
+                    )
+                )
+                .isEqualTo(AuthenticationResult.SUCCEEDED)
+        } else {
+            keyguardRepository.setKeyguardDismissible(true)
+        }
+        advanceTimeBy(UNLOCKED_DELAY_MS * 2) // wait for unlocked delay
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt
index 365a7c3..856c3fe 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt
@@ -195,6 +195,7 @@
                 eq(PACKAGE_NAME),
                 eq(true),
                 eq(dialogTransitionController),
+                eq(null),
                 eq(null)
             )
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
index bba9991..8b4265f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
@@ -23,8 +23,14 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
+import com.android.systemui.statusbar.notification.data.repository.notificationsKeyguardViewStateRepository
 import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor
 import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
 import com.android.systemui.statusbar.notification.stack.data.repository.setNotifications
@@ -41,9 +47,19 @@
 @RunWith(AndroidJUnit4::class)
 @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME)
 class HeadsUpNotificationInteractorTest : SysuiTestCase() {
-    private val kosmos = testKosmos()
+    private val kosmos =
+        testKosmos().apply {
+            fakeKeyguardTransitionRepository =
+                FakeKeyguardTransitionRepository(initInLockscreen = false)
+        }
     private val testScope = kosmos.testScope
-    private val repository = kosmos.headsUpNotificationRepository
+    private val faceAuthRepository by lazy { kosmos.fakeDeviceEntryFaceAuthRepository }
+    private val headsUpRepository by lazy { kosmos.headsUpNotificationRepository }
+    private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
+    private val keyguardViewStateRepository by lazy {
+        kosmos.notificationsKeyguardViewStateRepository
+    }
+    private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
 
     private val underTest = kosmos.headsUpNotificationInteractor
 
@@ -60,7 +76,7 @@
         testScope.runTest {
             val hasPinnedRows by collectLastValue(underTest.hasPinnedRows)
             // WHEN no pinned rows are set
-            repository.setNotifications(
+            headsUpRepository.setNotifications(
                 fakeHeadsUpRowRepository("key 0"),
                 fakeHeadsUpRowRepository("key 1"),
                 fakeHeadsUpRowRepository("key 2"),
@@ -76,7 +92,7 @@
         testScope.runTest {
             val hasPinnedRows by collectLastValue(underTest.hasPinnedRows)
             // WHEN a pinned rows is set
-            repository.setNotifications(
+            headsUpRepository.setNotifications(
                 fakeHeadsUpRowRepository("key 0", isPinned = true),
                 fakeHeadsUpRowRepository("key 1"),
                 fakeHeadsUpRowRepository("key 2"),
@@ -98,7 +114,7 @@
                     fakeHeadsUpRowRepository("key 1"),
                     fakeHeadsUpRowRepository("key 2"),
                 )
-            repository.setNotifications(rows)
+            headsUpRepository.setNotifications(rows)
             runCurrent()
 
             // WHEN a row gets pinned
@@ -120,7 +136,7 @@
                     fakeHeadsUpRowRepository("key 1"),
                     fakeHeadsUpRowRepository("key 2"),
                 )
-            repository.setNotifications(rows)
+            headsUpRepository.setNotifications(rows)
             runCurrent()
 
             // THEN that row gets unpinned
@@ -144,7 +160,7 @@
         testScope.runTest {
             val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows)
             // WHEN no rows are pinned
-            repository.setNotifications(
+            headsUpRepository.setNotifications(
                 fakeHeadsUpRowRepository("key 0"),
                 fakeHeadsUpRowRepository("key 1"),
                 fakeHeadsUpRowRepository("key 2"),
@@ -166,7 +182,7 @@
                     fakeHeadsUpRowRepository("key 1", isPinned = true),
                     fakeHeadsUpRowRepository("key 2"),
                 )
-            repository.setNotifications(rows)
+            headsUpRepository.setNotifications(rows)
             runCurrent()
 
             // THEN the unpinned rows are filtered
@@ -184,7 +200,7 @@
                     fakeHeadsUpRowRepository("key 1", isPinned = true),
                     fakeHeadsUpRowRepository("key 2"),
                 )
-            repository.setNotifications(rows)
+            headsUpRepository.setNotifications(rows)
             runCurrent()
 
             // WHEN all rows gets pinned
@@ -206,7 +222,7 @@
                     fakeHeadsUpRowRepository("key 1", isPinned = true),
                     fakeHeadsUpRowRepository("key 2", isPinned = true),
                 )
-            repository.setNotifications(rows)
+            headsUpRepository.setNotifications(rows)
             runCurrent()
 
             // THEN no rows are filtered
@@ -224,7 +240,7 @@
                     fakeHeadsUpRowRepository("key 1", isPinned = true),
                     fakeHeadsUpRowRepository("key 2", isPinned = true),
                 )
-            repository.setNotifications(rows)
+            headsUpRepository.setNotifications(rows)
             runCurrent()
 
             // WHEN a row gets unpinned
@@ -246,7 +262,7 @@
                     fakeHeadsUpRowRepository("key 1"),
                     fakeHeadsUpRowRepository("key 2"),
                 )
-            repository.setNotifications(rows)
+            headsUpRepository.setNotifications(rows)
             runCurrent()
 
             rows[0].isPinned.value = true
@@ -262,6 +278,96 @@
             assertThat(pinnedHeadsUpRows).containsExactly(rows[0])
         }
 
+    @Test
+    fun showHeadsUpStatusBar_true() =
+        testScope.runTest {
+            val showHeadsUpStatusBar by collectLastValue(underTest.showHeadsUpStatusBar)
+
+            // WHEN a row is pinned
+            headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true))
+
+            assertThat(showHeadsUpStatusBar).isTrue()
+        }
+
+    @Test
+    fun showHeadsUpStatusBar_withoutPinnedNotifications_false() =
+        testScope.runTest {
+            val showHeadsUpStatusBar by collectLastValue(underTest.showHeadsUpStatusBar)
+
+            // WHEN no row is pinned
+            headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = false))
+
+            assertThat(showHeadsUpStatusBar).isFalse()
+        }
+
+    @Test
+    fun showHeadsUpStatusBar_whenShadeExpanded_false() =
+        testScope.runTest {
+            val showHeadsUpStatusBar by collectLastValue(underTest.showHeadsUpStatusBar)
+
+            // WHEN a row is pinned
+            headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true))
+            // AND the shade is expanded
+            shadeTestUtil.setShadeExpansion(1.0f)
+
+            assertThat(showHeadsUpStatusBar).isFalse()
+        }
+
+    @Test
+    fun showHeadsUpStatusBar_notificationsAreHidden_false() =
+        testScope.runTest {
+            val showHeadsUpStatusBar by collectLastValue(underTest.showHeadsUpStatusBar)
+
+            // WHEN a row is pinned
+            headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true))
+            // AND the notifications are hidden
+            keyguardViewStateRepository.areNotificationsFullyHidden.value = true
+
+            assertThat(showHeadsUpStatusBar).isFalse()
+        }
+
+    @Test
+    fun showHeadsUpStatusBar_onLockScreen_false() =
+        testScope.runTest {
+            val showHeadsUpStatusBar by collectLastValue(underTest.showHeadsUpStatusBar)
+
+            // WHEN a row is pinned
+            headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true))
+            // AND the lock screen is shown
+            keyguardTransitionRepository.emitInitialStepsFromOff(to = KeyguardState.LOCKSCREEN)
+
+            assertThat(showHeadsUpStatusBar).isFalse()
+        }
+
+    @Test
+    fun showHeadsUpStatusBar_onByPassLockScreen_true() =
+        testScope.runTest {
+            val showHeadsUpStatusBar by collectLastValue(underTest.showHeadsUpStatusBar)
+
+            // WHEN a row is pinned
+            headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true))
+            // AND the lock screen is shown
+            keyguardTransitionRepository.emitInitialStepsFromOff(to = KeyguardState.LOCKSCREEN)
+            // AND bypass is enabled
+            faceAuthRepository.isBypassEnabled.value = true
+
+            assertThat(showHeadsUpStatusBar).isTrue()
+        }
+
+    @Test
+    fun showHeadsUpStatusBar_onByPassLockScreen_withoutNotifications_false() =
+        testScope.runTest {
+            val showHeadsUpStatusBar by collectLastValue(underTest.showHeadsUpStatusBar)
+
+            // WHEN no pinned rows
+            // AND the lock screen is shown
+            keyguardTransitionRepository.emitInitialStepsFromOff(to = KeyguardState.LOCKSCREEN)
+            // AND bypass is enabled
+            faceAuthRepository.isBypassEnabled.value = true
+
+            assertThat(showHeadsUpStatusBar).isFalse()
+        }
+
     private fun fakeHeadsUpRowRepository(key: String, isPinned: Boolean = false) =
         FakeHeadsUpRowRepository(key = key, elementKey = Any()).apply {
             this.isPinned.value = isPinned
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index cc5df74..9fde116 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -524,9 +524,9 @@
             // WHEN there are no pinned rows
             val rows =
                 arrayListOf(
-                    fakeHeadsUpRowRepository(key = "0"),
-                    fakeHeadsUpRowRepository(key = "1"),
-                    fakeHeadsUpRowRepository(key = "2"),
+                    FakeHeadsUpRowRepository(key = "0"),
+                    FakeHeadsUpRowRepository(key = "1"),
+                    FakeHeadsUpRowRepository(key = "2"),
                 )
             headsUpRepository.setNotifications(
                 rows,
@@ -565,8 +565,8 @@
             val hasPinnedHeadsUpRow by collectLastValue(underTest.hasPinnedHeadsUpRow)
 
             headsUpRepository.setNotifications(
-                fakeHeadsUpRowRepository(key = "0", isPinned = true),
-                fakeHeadsUpRowRepository(key = "1")
+                FakeHeadsUpRowRepository(key = "0", isPinned = true),
+                FakeHeadsUpRowRepository(key = "1")
             )
             runCurrent()
 
@@ -580,8 +580,8 @@
             val hasPinnedHeadsUpRow by collectLastValue(underTest.hasPinnedHeadsUpRow)
 
             headsUpRepository.setNotifications(
-                fakeHeadsUpRowRepository(key = "0"),
-                fakeHeadsUpRowRepository(key = "1"),
+                FakeHeadsUpRowRepository(key = "0"),
+                FakeHeadsUpRowRepository(key = "1"),
             )
             runCurrent()
 
@@ -607,7 +607,7 @@
             val animationsEnabled by collectLastValue(underTest.headsUpAnimationsEnabled)
 
             shadeTestUtil.setQsExpansion(0.0f)
-            fakeKeyguardRepository.setKeyguardShowing(false)
+            fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
             runCurrent()
 
             assertThat(animationsEnabled).isTrue()
@@ -620,14 +620,9 @@
             val animationsEnabled by collectLastValue(underTest.headsUpAnimationsEnabled)
 
             shadeTestUtil.setQsExpansion(0.0f)
-            fakeKeyguardRepository.setKeyguardShowing(true)
+            fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
             runCurrent()
 
             assertThat(animationsEnabled).isFalse()
         }
-
-    private fun fakeHeadsUpRowRepository(key: String, isPinned: Boolean = false) =
-        FakeHeadsUpRowRepository(key = key, elementKey = Any()).apply {
-            this.isPinned.value = isPinned
-        }
 }
diff --git a/packages/SystemUI/res/drawable/shelf_action_chip_container_background.xml b/packages/SystemUI/res/drawable/shelf_action_chip_container_background.xml
index bb8cece..ad6c154 100644
--- a/packages/SystemUI/res/drawable/shelf_action_chip_container_background.xml
+++ b/packages/SystemUI/res/drawable/shelf_action_chip_container_background.xml
@@ -14,10 +14,14 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<shape
+<inset
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-    android:shape="rectangle">
-    <solid android:color="?androidprv:attr/materialColorSurfaceBright"/>
-    <corners android:radius="10000dp"/>  <!-- fully-rounded radius -->
-</shape>
+    android:insetLeft="@dimen/overlay_action_container_minimum_edge_spacing"
+    android:insetRight="@dimen/overlay_action_container_minimum_edge_spacing">
+    <shape
+        xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+        android:shape="rectangle">
+        <solid android:color="?androidprv:attr/materialColorSurfaceBright"/>
+        <corners android:radius="10000dp"/>  <!-- fully-rounded radius -->
+    </shape>
+</inset>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/clipboard_overlay2.xml b/packages/SystemUI/res/layout/clipboard_overlay2.xml
index 521369e..65005f8 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay2.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay2.xml
@@ -24,6 +24,16 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:contentDescription="@string/clipboard_overlay_window_name">
+    <!-- Min edge spacing guideline off of which the preview and actions can be anchored (without
+         this we'd need to express margins as the sum of two different dimens). -->
+    <androidx.constraintlayout.widget.Guideline
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/min_edge_guideline"
+        app:layout_constraintGuide_begin="@dimen/overlay_action_container_minimum_edge_spacing"
+        android:orientation="vertical"/>
+    <!-- Negative horizontal margin because this container background must render beyond the thing
+         it's constrained by (the actions themselves). -->
     <FrameLayout
         android:id="@+id/actions_container_background"
         android:visibility="gone"
@@ -31,11 +41,12 @@
         android:layout_width="0dp"
         android:elevation="4dp"
         android:background="@drawable/shelf_action_chip_container_background"
-        android:layout_marginStart="@dimen/overlay_action_container_minimum_edge_spacing"
+        android:layout_marginStart="@dimen/negative_overlay_action_container_minimum_edge_spacing"
+        android:layout_marginEnd="@dimen/negative_overlay_action_container_minimum_edge_spacing"
         android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="@+id/actions_container"
-        app:layout_constraintEnd_toEndOf="@+id/actions_container"
+        app:layout_constraintStart_toStartOf="@id/min_edge_guideline"
+        app:layout_constraintTop_toTopOf="@id/actions_container"
+        app:layout_constraintEnd_toEndOf="@id/actions_container"
         app:layout_constraintBottom_toBottomOf="parent"/>
     <HorizontalScrollView
         android:id="@+id/actions_container"
@@ -76,7 +87,7 @@
         android:layout_marginBottom="@dimen/overlay_preview_container_margin"
         android:elevation="7dp"
         android:background="@drawable/overlay_border"
-        app:layout_constraintStart_toStartOf="@id/actions_container_background"
+        app:layout_constraintStart_toStartOf="@id/min_edge_guideline"
         app:layout_constraintTop_toTopOf="@id/clipboard_preview"
         app:layout_constraintEnd_toEndOf="@id/clipboard_preview"
         app:layout_constraintBottom_toBottomOf="@id/actions_container_background"/>
diff --git a/packages/SystemUI/res/layout/screenshot_shelf.xml b/packages/SystemUI/res/layout/screenshot_shelf.xml
index 6b65e9c..796def3 100644
--- a/packages/SystemUI/res/layout/screenshot_shelf.xml
+++ b/packages/SystemUI/res/layout/screenshot_shelf.xml
@@ -79,7 +79,6 @@
             android:layout_width="wrap_content"
             android:elevation="4dp"
             android:background="@drawable/shelf_action_chip_container_background"
-            android:layout_marginHorizontal="@dimen/overlay_action_container_minimum_edge_spacing"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintBottom_toTopOf="@id/guideline"
             >
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index b960813..02b74ce 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -449,8 +449,12 @@
     <dimen name="overlay_preview_container_margin">8dp</dimen>
     <dimen name="overlay_action_container_margin_horizontal">8dp</dimen>
     <dimen name="overlay_action_container_margin_bottom">6dp</dimen>
-    <!-- minimum distance to the left, right or bottom edges. -->
+    <!--
+        minimum distance to the left, right or bottom edges. Keep in sync with
+        negative_overlay_action_container_minimum_edge_spacing. -->
     <dimen name="overlay_action_container_minimum_edge_spacing">12dp</dimen>
+    <!-- Keep in sync with overlay_action_container_minimum_edge_spacing -->
+    <dimen name="negative_overlay_action_container_minimum_edge_spacing">-12dp</dimen>
     <dimen name="overlay_bg_protection_height">242dp</dimen>
     <dimen name="overlay_action_container_corner_radius">20dp</dimen>
     <dimen name="overlay_action_container_padding_vertical">8dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java
index 207f7db..f320057 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java
@@ -221,7 +221,8 @@
                 (view) -> {
                     // TODO: b/321969740 - Take the userHandle as a parameter and pass it through.
                     //  The package name is not sufficient to unambiguously identify an app.
-                    mMediaOutputDialogManager.createAndShow(mOutputPackageName, true, null, null);
+                    mMediaOutputDialogManager.createAndShow(
+                            mOutputPackageName, true, null, null, null);
                     dialog.dismiss();
                 });
         cancelBtn.setOnClickListener((view) -> {
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
index a32b2aa..6ca8eb9 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
@@ -36,6 +36,9 @@
 
     val authenticated: Flow<Boolean>
 
+    /** Whether bypass is enabled. If enabled, face unlock dismisses the lock screen. */
+    val isBypassEnabled: Flow<Boolean>
+
     /** Can face auth be run right now */
     fun canFaceAuthRun(): Boolean
 
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
index 6629f6e..9486798 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
@@ -22,6 +22,7 @@
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flowOf
 
 /**
  * Implementation of the interactor that noops all face auth operations.
@@ -35,6 +36,7 @@
     override val detectionStatus: Flow<FaceDetectionStatus> = emptyFlow()
     override val lockedOut: Flow<Boolean> = emptyFlow()
     override val authenticated: Flow<Boolean> = emptyFlow()
+    override val isBypassEnabled: Flow<Boolean> = flowOf(false)
 
     override fun canFaceAuthRun(): Boolean = false
 
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
index 669cd94..87f3f3c 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
@@ -286,6 +286,7 @@
     override val detectionStatus = repository.detectionStatus
     override val lockedOut: Flow<Boolean> = repository.isLockedOut
     override val authenticated: Flow<Boolean> = repository.isAuthenticated
+    override val isBypassEnabled: Flow<Boolean> = repository.isBypassEnabled
 
     private fun runFaceAuth(uiEvent: FaceAuthUiEvent, fallbackToDetect: Boolean) {
         if (repository.isLockedOut.value) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index 8d90933..fa43ec2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -207,19 +207,24 @@
             .distinctUntilChanged()
 
     private val isUnlocked: Flow<Boolean> =
-        keyguardInteractor.isKeyguardDismissible.flatMapLatest { isUnlocked ->
-            if (!isUnlocked) {
-                flowOf(false)
+        if (SceneContainerFlag.isEnabled) {
+                deviceEntryInteractor.isUnlocked
             } else {
-                flow {
-                    // delay in case device ends up transitioning away from the lock screen;
-                    // we don't want to animate to the unlocked icon and just let the
-                    // icon fade with the transition to GONE
-                    delay(UNLOCKED_DELAY_MS)
-                    emit(true)
+                keyguardInteractor.isKeyguardDismissible
+            }
+            .flatMapLatest { isUnlocked ->
+                if (!isUnlocked) {
+                    flowOf(false)
+                } else {
+                    flow {
+                        // delay in case device ends up transitioning away from the lock screen;
+                        // we don't want to animate to the unlocked icon and just let the
+                        // icon fade with the transition to GONE
+                        delay(UNLOCKED_DELAY_MS)
+                        emit(true)
+                    }
                 }
             }
-        }
 
     val iconType: Flow<DeviceEntryIconView.IconType> =
         combine(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
index 043fbfa..486d4d4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt
@@ -102,7 +102,8 @@
                 return
             }
             val controller = data.token?.let { controllerFactory.create(it) }
-            val localMediaManager = localMediaManagerFactory.create(data.packageName)
+            val localMediaManager =
+                localMediaManagerFactory.create(data.packageName, controller?.sessionToken)
             val muteAwaitConnectionManager =
                 muteAwaitConnectionManagerFactory.create(localMediaManager)
             entry = Entry(key, oldKey, controller, localMediaManager, muteAwaitConnectionManager)
@@ -224,9 +225,9 @@
         }
 
         @WorkerThread
-        override fun onAudioInfoChanged(info: MediaController.PlaybackInfo?) {
-            val newPlaybackType = info?.playbackType ?: PLAYBACK_TYPE_UNKNOWN
-            val newPlaybackVolumeControlId = info?.volumeControlId
+        override fun onAudioInfoChanged(info: MediaController.PlaybackInfo) {
+            val newPlaybackType = info.playbackType
+            val newPlaybackVolumeControlId = info.volumeControlId
             if (
                 newPlaybackType == playbackType &&
                     newPlaybackVolumeControlId == playbackVolumeControlId
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt
index 1a0f582..3f75938 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt
@@ -155,11 +155,16 @@
         return false
     }
 
-    fun startMediaOutputDialog(expandable: Expandable, packageName: String) {
+    fun startMediaOutputDialog(
+        expandable: Expandable,
+        packageName: String,
+        token: MediaSession.Token? = null
+    ) {
         mediaOutputDialogManager.createAndShowWithController(
             packageName,
             true,
-            expandable.dialogController()
+            expandable.dialogController(),
+            token = token,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index 0bc3c439..5ec4f88 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -743,7 +743,8 @@
                                     mPackageName,
                                     /* aboveStatusBar */ true,
                                     mMediaViewHolder.getSeamlessButton(),
-                                    UserHandle.getUserHandleForUid(mUid));
+                                    UserHandle.getUserHandleForUid(mUid),
+                                    mToken);
                         }
                     } else {
                         mLogger.logOpenOutputSwitcher(mUid, mPackageName, mInstanceId);
@@ -775,7 +776,8 @@
                                     mPackageName,
                                     /* aboveStatusBar */ true,
                                     mMediaViewHolder.getSeamlessButton(),
-                                    UserHandle.getUserHandleForUid(mUid));
+                                    UserHandle.getUserHandleForUid(mUid),
+                                    mToken);
                         }
                     }
                 });
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
index 1944f07..099991d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
@@ -231,12 +231,20 @@
                         )
                     } else {
                         logger.logOpenOutputSwitcher(model.uid, model.packageName, model.instanceId)
-                        interactor.startMediaOutputDialog(expandable, model.packageName)
+                        interactor.startMediaOutputDialog(
+                            expandable,
+                            model.packageName,
+                            model.token
+                        )
                     }
                 } else {
                     logger.logOpenOutputSwitcher(model.uid, model.packageName, model.instanceId)
                     device?.intent?.let { interactor.startDeviceIntent(it) }
-                        ?: interactor.startMediaOutputDialog(expandable, model.packageName)
+                        ?: interactor.startMediaOutputDialog(
+                            expandable,
+                            model.packageName,
+                            model.token
+                        )
                 }
             }
         )
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt
index ff8e903b..0a717ad 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.media.controls.util
 
 import android.content.Context
+import android.media.session.MediaSession
 import com.android.settingslib.bluetooth.LocalBluetoothManager
 import com.android.settingslib.media.InfoMediaManager
 import com.android.settingslib.media.LocalMediaManager
@@ -30,10 +31,16 @@
     private val localBluetoothManager: LocalBluetoothManager?
 ) {
     /** Creates a [LocalMediaManager] for the given package. */
-    fun create(packageName: String?): LocalMediaManager {
+    fun create(packageName: String?, token: MediaSession.Token? = null): LocalMediaManager {
         // TODO: b/321969740 - Populate the userHandle parameter in InfoMediaManager. The user
         // handle is necessary to disambiguate the same package running on different users.
-        return InfoMediaManager.createInstance(context, packageName, null, localBluetoothManager)
+        return InfoMediaManager.createInstance(
+                context,
+                packageName,
+                null,
+                localBluetoothManager,
+                token
+            )
             .run { LocalMediaManager(context, localBluetoothManager, this, packageName) }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt
index 06267e2..6ef9ea3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt
@@ -40,7 +40,12 @@
 
         // TODO: b/321969740 - Populate the userHandle parameter. The user handle is necessary to
         //  disambiguate the same package running on different users.
-        val controller = mediaOutputControllerFactory.create(packageName, /* userHandle= */ null)
+        val controller =
+            mediaOutputControllerFactory.create(
+                packageName,
+                /* userHandle= */ null,
+                /* token */ null,
+            )
         val dialog =
             MediaOutputBroadcastDialog(context, aboveStatusBar, broadcastSender, controller)
         mediaOutputBroadcastDialog = dialog
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index d6ca320..c2cfdbe 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -78,6 +78,7 @@
 import com.android.settingslib.media.InfoMediaManager;
 import com.android.settingslib.media.LocalMediaManager;
 import com.android.settingslib.media.MediaDevice;
+import com.android.settingslib.media.flags.Flags;
 import com.android.settingslib.utils.ThreadUtils;
 import com.android.systemui.animation.ActivityTransitionAnimator;
 import com.android.systemui.animation.DialogTransitionAnimator;
@@ -141,6 +142,7 @@
     private final KeyguardManager mKeyGuardManager;
     private final NearbyMediaDevicesManager mNearbyMediaDevicesManager;
     private final Map<String, Integer> mNearbyDeviceInfoMap = new ConcurrentHashMap<>();
+    private final MediaSession.Token mToken;
 
     @VisibleForTesting
     boolean mIsRefreshing = false;
@@ -179,6 +181,7 @@
             Context context,
             @Assisted String packageName,
             @Assisted @Nullable UserHandle userHandle,
+            @Assisted @Nullable MediaSession.Token token,
             MediaSessionManager mediaSessionManager,
             @Nullable LocalBluetoothManager lbm,
             ActivityStarter starter,
@@ -202,8 +205,9 @@
         mKeyGuardManager = keyGuardManager;
         mFeatureFlags = featureFlags;
         mUserTracker = userTracker;
+        mToken = token;
         InfoMediaManager imm =
-                InfoMediaManager.createInstance(mContext, packageName, userHandle, lbm);
+                InfoMediaManager.createInstance(mContext, packageName, userHandle, lbm, token);
         mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName);
         mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName);
         mDialogTransitionAnimator = dialogTransitionAnimator;
@@ -235,7 +239,8 @@
     @AssistedFactory
     public interface Factory {
         /** Construct a MediaOutputController */
-        MediaOutputController create(String packageName, UserHandle userHandle);
+        MediaOutputController create(
+                String packageName, UserHandle userHandle, MediaSession.Token token);
     }
 
     protected void start(@NonNull Callback cb) {
@@ -297,23 +302,28 @@
     }
 
     private MediaController getMediaController() {
-        for (NotificationEntry entry : mNotifCollection.getAllNotifs()) {
-            final Notification notification = entry.getSbn().getNotification();
-            if (notification.isMediaNotification()
-                    && TextUtils.equals(entry.getSbn().getPackageName(), mPackageName)) {
-                MediaSession.Token token = notification.extras.getParcelable(
-                        Notification.EXTRA_MEDIA_SESSION,
-                        MediaSession.Token.class);
-                return new MediaController(mContext, token);
+        if (mToken != null && Flags.usePlaybackInfoForRoutingControls()) {
+            return new MediaController(mContext, mToken);
+        } else {
+            for (NotificationEntry entry : mNotifCollection.getAllNotifs()) {
+                final Notification notification = entry.getSbn().getNotification();
+                if (notification.isMediaNotification()
+                        && TextUtils.equals(entry.getSbn().getPackageName(), mPackageName)) {
+                    MediaSession.Token token =
+                            notification.extras.getParcelable(
+                                    Notification.EXTRA_MEDIA_SESSION, MediaSession.Token.class);
+                    return new MediaController(mContext, token);
+                }
             }
-        }
-        for (MediaController controller : mMediaSessionManager.getActiveSessionsForUser(null,
-                mUserTracker.getUserHandle())) {
-            if (TextUtils.equals(controller.getPackageName(), mPackageName)) {
-                return controller;
+            for (MediaController controller :
+                    mMediaSessionManager.getActiveSessionsForUser(
+                            null, mUserTracker.getUserHandle())) {
+                if (TextUtils.equals(controller.getPackageName(), mPackageName)) {
+                    return controller;
+                }
             }
+            return null;
         }
-        return null;
     }
 
     @Override
@@ -869,10 +879,6 @@
         mMetricLogger.logInteractionUnmute(device);
     }
 
-    String getPackageName() {
-        return mPackageName;
-    }
-
     boolean hasAdjustVolumeUserRestriction() {
         if (RestrictedLockUtilsInternal.checkIfRestrictionEnforced(
                 mContext, UserManager.DISALLOW_ADJUST_VOLUME, UserHandle.myUserId()) != null) {
@@ -955,6 +961,7 @@
                         mContext,
                         mPackageName,
                         mUserHandle,
+                        mToken,
                         mMediaSessionManager,
                         mLocalBluetoothManager,
                         mActivityStarter,
@@ -1060,7 +1067,7 @@
     boolean isBroadcastSupported() {
         LocalBluetoothLeBroadcast broadcast =
                 mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile();
-        return broadcast != null ? true : false;
+        return broadcast != null;
     }
 
     boolean isBluetoothLeBroadcastEnabled() {
@@ -1194,13 +1201,6 @@
         assistant.unregisterServiceCallBack(callback);
     }
 
-    private boolean isPlayBackInfoLocal() {
-        return mMediaController != null
-                && mMediaController.getPlaybackInfo() != null
-                && mMediaController.getPlaybackInfo().getPlaybackType()
-                == MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL;
-    }
-
     boolean isPlaying() {
         if (mMediaController == null) {
             return false;
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
index 04d1492..ee816942 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.media.dialog
 
 import android.content.Context
+import android.media.session.MediaSession
 import android.os.UserHandle
 import android.view.View
 import com.android.internal.jank.InteractionJankMonitor
@@ -49,7 +50,8 @@
         packageName: String,
         aboveStatusBar: Boolean,
         view: View? = null,
-        userHandle: UserHandle? = null
+        userHandle: UserHandle? = null,
+        token: MediaSession.Token? = null
     ) {
         createAndShowWithController(
             packageName,
@@ -65,6 +67,7 @@
                     )
                 },
             userHandle = userHandle,
+            token = token,
         )
     }
 
@@ -77,6 +80,7 @@
         aboveStatusBar: Boolean,
         controller: DialogTransitionAnimator.Controller?,
         userHandle: UserHandle? = null,
+        token: MediaSession.Token? = null,
     ) {
         createAndShow(
             packageName,
@@ -84,6 +88,7 @@
             dialogTransitionAnimatorController = controller,
             includePlaybackAndAppMetadata = true,
             userHandle = userHandle,
+            token = token,
         )
     }
 
@@ -108,11 +113,12 @@
         dialogTransitionAnimatorController: DialogTransitionAnimator.Controller?,
         includePlaybackAndAppMetadata: Boolean = true,
         userHandle: UserHandle? = null,
+        token: MediaSession.Token? = null,
     ) {
         // Dismiss the previous dialog, if any.
         mediaOutputDialog?.dismiss()
 
-        val controller = mediaOutputControllerFactory.create(packageName, userHandle)
+        val controller = mediaOutputControllerFactory.create(packageName, userHandle, token)
 
         val mediaOutputDialog =
             MediaOutputDialog(
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java
index 9cc2888..846460e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java
@@ -56,7 +56,11 @@
     public void showMediaOutputSwitcher(String packageName, UserHandle userHandle) {
         if (!TextUtils.isEmpty(packageName)) {
             mMediaOutputDialogManager.createAndShow(
-                    packageName, /* aboveStatusBar= */ false, /* view= */ null, userHandle);
+                    packageName,
+                    /* aboveStatusBar= */ false,
+                    /* view= */ null,
+                    userHandle,
+                    /* token */ null);
         } else {
             Log.e(TAG, "Unable to launch media output dialog. Package name is empty.");
         }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index 9dc19b1..daea977 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -181,11 +181,8 @@
 
     /**
      * Returns true if heads up should be visible.
-     *
-     * TODO(b/138786270): If HeadsUpAppearanceController was injectable, we could inject it into
-     *   [KeyguardStatusBarViewController] and remove this method.
      */
-    @Deprecated("deprecated in Flexiglass.") fun shouldHeadsUpBeVisible(): Boolean
+    @Deprecated("deprecated by SceneContainerFlag.isEnabled.") fun shouldHeadsUpBeVisible(): Boolean
 
     /** Return the fraction of the shade that's expanded, when in lockscreen. */
     @Deprecated("deprecated by SceneContainerFlag.isEnabled") val lockscreenShadeDragProgress: Float
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index 0de3c10..18407cc 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -38,6 +38,9 @@
     /** Whether the Shade is fully expanded. */
     val isShadeFullyExpanded: Flow<Boolean>
 
+    /** Whether the Shade is fully collapsed. */
+    val isShadeFullyCollapsed: Flow<Boolean>
+
     /**
      * Whether the user is expanding or collapsing either the shade or quick settings with user
      * input (i.e. dragging a pointer). This will be true even if the user's input gesture had ended
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
index 883ef97..bb4baa3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
@@ -38,6 +38,7 @@
     override val anyExpansion: StateFlow<Float> = inactiveFlowFloat
     override val isAnyFullyExpanded: StateFlow<Boolean> = inactiveFlowBoolean
     override val isShadeFullyExpanded: Flow<Boolean> = inactiveFlowBoolean
+    override val isShadeFullyCollapsed: Flow<Boolean> = inactiveFlowBoolean
     override val isAnyExpanded: StateFlow<Boolean> = inactiveFlowBoolean
     override val isUserInteractingWithShade: Flow<Boolean> = inactiveFlowBoolean
     override val isUserInteractingWithQs: Flow<Boolean> = inactiveFlowBoolean
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
index 0b45c08..06a8d18 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
@@ -74,6 +74,9 @@
     override val isShadeFullyExpanded: Flow<Boolean> =
         baseShadeInteractor.shadeExpansion.map { it >= 1f }.distinctUntilChanged()
 
+    override val isShadeFullyCollapsed: Flow<Boolean> =
+        baseShadeInteractor.shadeExpansion.map { it <= 0f }.distinctUntilChanged()
+
     override val isUserInteracting: StateFlow<Boolean> =
         combine(isUserInteractingWithShade, isUserInteractingWithQs) { shade, qs -> shade || qs }
             .distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
index 98b52ed..4a6553f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
@@ -18,6 +18,10 @@
 
 package com.android.systemui.statusbar.notification.domain.interactor
 
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository
 import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRepository
 import com.android.systemui.statusbar.notification.shared.HeadsUpRowKey
@@ -29,13 +33,21 @@
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 
-class HeadsUpNotificationInteractor @Inject constructor(private val repository: HeadsUpRepository) {
+class HeadsUpNotificationInteractor
+@Inject
+constructor(
+    private val headsUpRepository: HeadsUpRepository,
+    private val faceAuthInteractor: DeviceEntryFaceAuthInteractor,
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
+    private val shadeInteractor: ShadeInteractor,
+) {
 
-    val topHeadsUpRow: Flow<HeadsUpRowKey?> = repository.topHeadsUpRow
+    val topHeadsUpRow: Flow<HeadsUpRowKey?> = headsUpRepository.topHeadsUpRow
 
     /** Set of currently pinned top-level heads up rows to be displayed. */
     val pinnedHeadsUpRows: Flow<Set<HeadsUpRowKey>> =
-        repository.activeHeadsUpRows.flatMapLatest { repositories ->
+        headsUpRepository.activeHeadsUpRows.flatMapLatest { repositories ->
             if (repositories.isNotEmpty()) {
                 val toCombine: List<Flow<Pair<HeadsUpRowRepository, Boolean>>> =
                     repositories.map { repo -> repo.isPinned.map { isPinned -> repo to isPinned } }
@@ -50,7 +62,7 @@
 
     /** Are there any pinned heads up rows to display? */
     val hasPinnedRows: Flow<Boolean> =
-        repository.activeHeadsUpRows.flatMapLatest { rows ->
+        headsUpRepository.activeHeadsUpRows.flatMapLatest { rows ->
             if (rows.isNotEmpty()) {
                 combine(rows.map { it.isPinned }) { pins -> pins.any { it } }
             } else {
@@ -60,15 +72,38 @@
         }
 
     val isHeadsUpOrAnimatingAway: Flow<Boolean> =
-        combine(hasPinnedRows, repository.isHeadsUpAnimatingAway) { hasPinnedRows, animatingAway ->
+        combine(hasPinnedRows, headsUpRepository.isHeadsUpAnimatingAway) {
+            hasPinnedRows,
+            animatingAway ->
             hasPinnedRows || animatingAway
         }
 
+    private val canShowHeadsUp: Flow<Boolean> =
+        combine(
+            faceAuthInteractor.isBypassEnabled,
+            shadeInteractor.isShadeFullyCollapsed,
+            keyguardTransitionInteractor.currentKeyguardState,
+            notificationsKeyguardInteractor.areNotificationsFullyHidden,
+        ) { isBypassEnabled, isShadeCollapsed, keyguardState, areNotificationsHidden ->
+            val isOnLockScreen = keyguardState == KeyguardState.LOCKSCREEN
+            when {
+                areNotificationsHidden -> false // don't show when notification are hidden
+                !isShadeCollapsed -> false // don't show when the shade is expanded
+                isOnLockScreen -> isBypassEnabled // on the lock screen only show for bypass
+                else -> true // show otherwise
+            }
+        }
+
+    val showHeadsUpStatusBar: Flow<Boolean> =
+        combine(hasPinnedRows, canShowHeadsUp) { hasPinnedRows, canShowHeadsUp ->
+            hasPinnedRows && canShowHeadsUp
+        }
+
     fun headsUpRow(key: HeadsUpRowKey): HeadsUpRowInteractor =
         HeadsUpRowInteractor(key as HeadsUpRowRepository)
     fun elementKeyFor(key: HeadsUpRowKey) = (key as HeadsUpRowRepository).elementKey
     fun setHeadsUpAnimatingAway(animatingAway: Boolean) {
-        repository.setHeadsUpAnimatingAway(animatingAway)
+        headsUpRepository.setHeadsUpAnimatingAway(animatingAway)
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
index 3a89630..b54f9c4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -18,7 +18,6 @@
 
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
@@ -59,7 +58,6 @@
     activeNotificationsInteractor: ActiveNotificationsInteractor,
     notificationStackInteractor: NotificationStackInteractor,
     private val headsUpNotificationInteractor: HeadsUpNotificationInteractor,
-    keyguardInteractor: KeyguardInteractor,
     remoteInputInteractor: RemoteInputInteractor,
     seenNotificationsInteractor: SeenNotificationsInteractor,
     shadeInteractor: ShadeInteractor,
@@ -277,11 +275,12 @@
         if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) {
             flowOf(false)
         } else {
-            combine(keyguardInteractor.isKeyguardShowing, shadeInteractor.isShadeFullyExpanded) {
-                    (isKeyguardShowing, isShadeFullyExpanded) ->
-                    // TODO(b/325936094) use isShadeFullyCollapsed instead
-                    !isKeyguardShowing && !isShadeFullyExpanded
-                }
+            combine(
+                notificationStackInteractor.isShowingOnLockscreen,
+                shadeInteractor.isShadeFullyCollapsed
+            ) { (isKeyguardShowing, isShadeFullyCollapsed) ->
+                !isKeyguardShowing && isShadeFullyCollapsed
+            }
                 .dumpWhileCollecting("headsUpAnimationsEnabled")
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index 4c3c7d5..11feb97 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -354,6 +354,7 @@
      * since the headsUp manager might not have notified us yet of the state change.
      *
      * @return if the heads up status bar view should be shown
+     * @deprecated use HeadsUpNotificationInteractor.showHeadsUpStatusBar instead.
      */
     public boolean shouldBeVisible() {
         boolean notificationsShown = !mWakeUpCoordinator.getNotificationsFullyHidden();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 2b26e3f..f767262 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -660,10 +660,14 @@
      * whether heads up is visible.
      */
     public void updateForHeadsUp() {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
+            // [KeyguardStatusBarViewBinder] handles visibility changes due to heads up states.
+            return;
+        }
         updateForHeadsUp(true);
     }
 
-    // TODO(b/328579846) bind the StatusBar visibility to heads up events
+    @VisibleForTesting
     void updateForHeadsUp(boolean animate) {
         boolean showingKeyguardHeadsUp =
                 isKeyguardShowing() && mShadeViewStateProvider.shouldHeadsUpBeVisible();
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 a858fb0..e5c86c8 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
@@ -548,6 +548,7 @@
 
     private StatusBarVisibilityModel calculateInternalModel(
             StatusBarVisibilityModel externalModel) {
+        // TODO(b/328393714) use HeadsUpNotificationInteractor.showHeadsUpStatusBar instead.
         boolean headsUpVisible =
                 mStatusBarFragmentComponent.getHeadsUpAppearanceController().shouldBeVisible();
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
index 5da01e2..3e01180 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.statusbar.domain.interactor.KeyguardStatusBarInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
 import javax.inject.Inject
@@ -46,6 +47,7 @@
 @Inject
 constructor(
     @Application scope: CoroutineScope,
+    headsUpNotificationInteractor: HeadsUpNotificationInteractor,
     keyguardInteractor: KeyguardInteractor,
     keyguardStatusBarInteractor: KeyguardStatusBarInteractor,
     batteryController: BatteryController,
@@ -55,8 +57,9 @@
         combine(
                 keyguardInteractor.isDozing,
                 keyguardInteractor.statusBarState,
-            ) { isDozing, statusBarState ->
-                !isDozing && statusBarState == StatusBarState.KEYGUARD
+                headsUpNotificationInteractor.showHeadsUpStatusBar,
+            ) { isDozing, statusBarState, showHeadsUpStatusBar ->
+                !isDozing && statusBarState == StatusBarState.KEYGUARD && !showHeadsUpStatusBar
             }
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java
deleted file mode 100644
index aeed78a..0000000
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java
+++ /dev/null
@@ -1,432 +0,0 @@
-/*
- * Copyright (C) 2020 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.settings;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.ContentResolver;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.provider.Settings;
-
-/**
- * Used to interact with mainly with Settings.Global, but can also be used for Settings.System
- * and Settings.Secure. To use the per-user System and Secure settings, {@link UserSettingsProxy}
- * must be used instead.
- * <p>
- * This interface can be implemented to give instance method (instead of static method) versions
- * of Settings.Global. It can be injected into class constructors and then faked or mocked as needed
- * in tests.
- * <p>
- * You can ask for {@link GlobalSettings} to be injected as needed.
- * <p>
- * This class also provides {@link #registerContentObserver(String, ContentObserver)} methods,
- * normally found on {@link ContentResolver} instances, unifying setting related actions in one
- * place.
- */
-public interface SettingsProxy {
-
-    /**
-     * Returns the {@link ContentResolver} this instance was constructed with.
-     */
-    ContentResolver getContentResolver();
-
-    /**
-     * Construct the content URI for a particular name/value pair,
-     * useful for monitoring changes with a ContentObserver.
-     * @param name to look up in the table
-     * @return the corresponding content URI, or null if not present
-     */
-    Uri getUriFor(String name);
-
-    /**
-     * Convenience wrapper around
-     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.'
-     * <p>
-     * Implicitly calls {@link #getUriFor(String)} on the passed in name.
-     */
-    default void registerContentObserver(String name, ContentObserver settingsObserver) {
-        registerContentObserver(getUriFor(name), settingsObserver);
-    }
-
-    /**
-     * Convenience wrapper around
-     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.'
-     */
-    default void registerContentObserver(Uri uri, ContentObserver settingsObserver) {
-        registerContentObserver(uri, false, settingsObserver);
-    }
-
-    /**
-     * Convenience wrapper around
-     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.'
-     * <p>
-     * Implicitly calls {@link #getUriFor(String)} on the passed in name.
-     */
-    default void registerContentObserver(String name, boolean notifyForDescendants,
-            ContentObserver settingsObserver) {
-        registerContentObserver(getUriFor(name), notifyForDescendants, settingsObserver);
-    }
-
-    /**
-     * Convenience wrapper around
-     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.'
-     */
-    default void registerContentObserver(Uri uri, boolean notifyForDescendants,
-            ContentObserver settingsObserver) {
-        getContentResolver().registerContentObserver(
-                uri, notifyForDescendants, settingsObserver);
-    }
-
-    /** See {@link ContentResolver#unregisterContentObserver(ContentObserver)}. */
-    default void unregisterContentObserver(ContentObserver settingsObserver) {
-        getContentResolver().unregisterContentObserver(settingsObserver);
-    }
-
-    /**
-     * Look up a name in the database.
-     * @param name to look up in the table
-     * @return the corresponding value, or null if not present
-     */
-    @Nullable
-    String getString(String name);
-
-    /**
-     * Store a name/value pair into the database.
-     * @param name to store
-     * @param value to associate with the name
-     * @return true if the value was set, false on database errors
-     */
-    boolean putString(String name, String value);
-
-    /**
-     * Store a name/value pair into the database.
-     * <p>
-     * The method takes an optional tag to associate with the setting
-     * which can be used to clear only settings made by your package and
-     * associated with this tag by passing the tag to {@link
-     * #resetToDefaults(String)}. Anyone can override
-     * the current tag. Also if another package changes the setting
-     * then the tag will be set to the one specified in the set call
-     * which can be null. Also any of the settings setters that do not
-     * take a tag as an argument effectively clears the tag.
-     * </p><p>
-     * For example, if you set settings A and B with tags T1 and T2 and
-     * another app changes setting A (potentially to the same value), it
-     * can assign to it a tag T3 (note that now the package that changed
-     * the setting is not yours). Now if you reset your changes for T1 and
-     * T2 only setting B will be reset and A not (as it was changed by
-     * another package) but since A did not change you are in the desired
-     * initial state. Now if the other app changes the value of A (assuming
-     * you registered an observer in the beginning) you would detect that
-     * the setting was changed by another app and handle this appropriately
-     * (ignore, set back to some value, etc).
-     * </p><p>
-     * Also the method takes an argument whether to make the value the
-     * default for this setting. If the system already specified a default
-     * value, then the one passed in here will <strong>not</strong>
-     * be set as the default.
-     * </p>
-     *
-     * @param name to store.
-     * @param value to associate with the name.
-     * @param tag to associate with the setting.
-     * @param makeDefault whether to make the value the default one.
-     * @return true if the value was set, false on database errors.
-     *
-     * @see #resetToDefaults(String)
-     *
-     */
-    boolean putString(@NonNull String name, @Nullable String value, @Nullable String tag,
-            boolean makeDefault);
-
-    /**
-     * Convenience function for retrieving a single secure settings value
-     * as an integer.  Note that internally setting values are always
-     * stored as strings; this function converts the string to an integer
-     * for you.  The default value will be returned if the setting is
-     * not defined or not an integer.
-     *
-     * @param name The name of the setting to retrieve.
-     * @param def Value to return if the setting is not defined.
-     *
-     * @return The setting's current value, or 'def' if it is not defined
-     * or not a valid integer.
-     */
-    default int getInt(String name, int def) {
-        String v = getString(name);
-        try {
-            return v != null ? Integer.parseInt(v) : def;
-        } catch (NumberFormatException e) {
-            return def;
-        }
-    }
-
-    /**
-     * Convenience function for retrieving a single secure settings value
-     * as an integer.  Note that internally setting values are always
-     * stored as strings; this function converts the string to an integer
-     * for you.
-     * <p>
-     * This version does not take a default value.  If the setting has not
-     * been set, or the string value is not a number,
-     * it throws {@link Settings.SettingNotFoundException}.
-     *
-     * @param name The name of the setting to retrieve.
-     *
-     * @throws Settings.SettingNotFoundException Thrown if a setting by the given
-     * name can't be found or the setting value is not an integer.
-     *
-     * @return The setting's current value.
-     */
-    default int getInt(String name)
-            throws Settings.SettingNotFoundException {
-        String v = getString(name);
-        try {
-            return Integer.parseInt(v);
-        } catch (NumberFormatException e) {
-            throw new Settings.SettingNotFoundException(name);
-        }
-    }
-
-    /**
-     * Convenience function for updating a single settings value as an
-     * integer. This will either create a new entry in the table if the
-     * given name does not exist, or modify the value of the existing row
-     * with that name.  Note that internally setting values are always
-     * stored as strings, so this function converts the given value to a
-     * string before storing it.
-     *
-     * @param name The name of the setting to modify.
-     * @param value The new value for the setting.
-     * @return true if the value was set, false on database errors
-     */
-    default boolean putInt(String name, int value) {
-        return putString(name, Integer.toString(value));
-    }
-
-    /**
-     * Convenience function for retrieving a single secure settings value
-     * as a boolean.  Note that internally setting values are always
-     * stored as strings; this function converts the string to a boolean
-     * for you.  The default value will be returned if the setting is
-     * not defined or not a boolean.
-     *
-     * @param name The name of the setting to retrieve.
-     * @param def Value to return if the setting is not defined.
-     *
-     * @return The setting's current value, or 'def' if it is not defined
-     * or not a valid boolean.
-     */
-    default boolean getBool(String name, boolean def) {
-        return getInt(name, def ? 1 : 0) != 0;
-    }
-
-    /**
-     * Convenience function for retrieving a single secure settings value
-     * as a boolean.  Note that internally setting values are always
-     * stored as strings; this function converts the string to a boolean
-     * for you.
-     * <p>
-     * This version does not take a default value.  If the setting has not
-     * been set, or the string value is not a number,
-     * it throws {@link Settings.SettingNotFoundException}.
-     *
-     * @param name The name of the setting to retrieve.
-     *
-     * @throws Settings.SettingNotFoundException Thrown if a setting by the given
-     * name can't be found or the setting value is not a boolean.
-     *
-     * @return The setting's current value.
-     */
-    default boolean getBool(String name)
-            throws Settings.SettingNotFoundException {
-        return getInt(name) != 0;
-    }
-
-    /**
-     * Convenience function for updating a single settings value as a
-     * boolean. This will either create a new entry in the table if the
-     * given name does not exist, or modify the value of the existing row
-     * with that name.  Note that internally setting values are always
-     * stored as strings, so this function converts the given value to a
-     * string before storing it.
-     *
-     * @param name The name of the setting to modify.
-     * @param value The new value for the setting.
-     * @return true if the value was set, false on database errors
-     */
-    default boolean putBool(String name, boolean value) {
-        return putInt(name, value ? 1 : 0);
-    }
-
-    /**
-     * Convenience function for retrieving a single secure settings value
-     * as a {@code long}.  Note that internally setting values are always
-     * stored as strings; this function converts the string to a {@code long}
-     * for you.  The default value will be returned if the setting is
-     * not defined or not a {@code long}.
-     *
-     * @param name The name of the setting to retrieve.
-     * @param def Value to return if the setting is not defined.
-     *
-     * @return The setting's current value, or 'def' if it is not defined
-     * or not a valid {@code long}.
-     */
-    default long getLong(String name, long def) {
-        String valString = getString(name);
-        return parseLongOrUseDefault(valString, def);
-    }
-
-    /** Convert a string to a long, or uses a default if the string is malformed or null */
-    static long parseLongOrUseDefault(String valString, long def) {
-        long value;
-        try {
-            value = valString != null ? Long.parseLong(valString) : def;
-        } catch (NumberFormatException e) {
-            value = def;
-        }
-        return value;
-    }
-
-    /**
-     * Convenience function for retrieving a single secure settings value
-     * as a {@code long}.  Note that internally setting values are always
-     * stored as strings; this function converts the string to a {@code long}
-     * for you.
-     * <p>
-     * This version does not take a default value.  If the setting has not
-     * been set, or the string value is not a number,
-     * it throws {@link Settings.SettingNotFoundException}.
-     *
-     * @param name The name of the setting to retrieve.
-     *
-     * @return The setting's current value.
-     * @throws Settings.SettingNotFoundException Thrown if a setting by the given
-     * name can't be found or the setting value is not an integer.
-     */
-    default long getLong(String name)
-            throws Settings.SettingNotFoundException {
-        String valString = getString(name);
-        return parseLongOrThrow(name, valString);
-    }
-
-    /** Convert a string to a long, or throws an exception if the string is malformed or null */
-    static long parseLongOrThrow(String name, String valString)
-            throws Settings.SettingNotFoundException {
-        try {
-            return Long.parseLong(valString);
-        } catch (NumberFormatException e) {
-            throw new Settings.SettingNotFoundException(name);
-        }
-    }
-
-    /**
-     * Convenience function for updating a secure settings value as a long
-     * integer. This will either create a new entry in the table if the
-     * given name does not exist, or modify the value of the existing row
-     * with that name.  Note that internally setting values are always
-     * stored as strings, so this function converts the given value to a
-     * string before storing it.
-     *
-     * @param name The name of the setting to modify.
-     * @param value The new value for the setting.
-     * @return true if the value was set, false on database errors
-     */
-    default boolean putLong(String name, long value) {
-        return putString(name, Long.toString(value));
-    }
-
-    /**
-     * Convenience function for retrieving a single secure settings value
-     * as a floating point number.  Note that internally setting values are
-     * always stored as strings; this function converts the string to an
-     * float for you. The default value will be returned if the setting
-     * is not defined or not a valid float.
-     *
-     * @param name The name of the setting to retrieve.
-     * @param def Value to return if the setting is not defined.
-     *
-     * @return The setting's current value, or 'def' if it is not defined
-     * or not a valid float.
-     */
-    default float getFloat(String name, float def) {
-        String v = getString(name);
-        return parseFloat(v, def);
-    }
-
-    /** Convert a string to a float, or uses a default if the string is malformed or null */
-    static float parseFloat(String v, float def) {
-        try {
-            return v != null ? Float.parseFloat(v) : def;
-        } catch (NumberFormatException e) {
-            return def;
-        }
-    }
-
-    /**
-     * Convenience function for retrieving a single secure settings value
-     * as a float.  Note that internally setting values are always
-     * stored as strings; this function converts the string to a float
-     * for you.
-     * <p>
-     * This version does not take a default value.  If the setting has not
-     * been set, or the string value is not a number,
-     * it throws {@link Settings.SettingNotFoundException}.
-     *
-     * @param name The name of the setting to retrieve.
-     *
-     * @throws Settings.SettingNotFoundException Thrown if a setting by the given
-     * name can't be found or the setting value is not a float.
-     *
-     * @return The setting's current value.
-     */
-    default float getFloat(String name)
-            throws Settings.SettingNotFoundException {
-        String v = getString(name);
-        return parseFloatOrThrow(name, v);
-    }
-
-    /** Convert a string to a float, or throws an exception if the string is malformed or null */
-    static float parseFloatOrThrow(String name, String v)
-            throws Settings.SettingNotFoundException {
-        if (v == null) {
-            throw new Settings.SettingNotFoundException(name);
-        }
-        try {
-            return Float.parseFloat(v);
-        } catch (NumberFormatException e) {
-            throw new Settings.SettingNotFoundException(name);
-        }
-    }
-
-    /**
-     * Convenience function for updating a single settings value as a
-     * floating point number. This will either create a new entry in the
-     * table if the given name does not exist, or modify the value of the
-     * existing row with that name.  Note that internally setting values
-     * are always stored as strings, so this function converts the given
-     * value to a string before storing it.
-     *
-     * @param name The name of the setting to modify.
-     * @param value The new value for the setting.
-     * @return true if the value was set, false on database errors
-     */
-    default boolean putFloat(String name, float value) {
-        return putString(name, Float.toString(value));
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
new file mode 100644
index 0000000..ec89610
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2020 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.settings
+
+import android.content.ContentResolver
+import android.database.ContentObserver
+import android.net.Uri
+import android.provider.Settings.SettingNotFoundException
+
+/**
+ * Used to interact with mainly with Settings.Global, but can also be used for Settings.System and
+ * Settings.Secure. To use the per-user System and Secure settings, [UserSettingsProxy] must be used
+ * instead.
+ *
+ * This interface can be implemented to give instance method (instead of static method) versions of
+ * Settings.Global. It can be injected into class constructors and then faked or mocked as needed in
+ * tests.
+ *
+ * You can ask for [GlobalSettings] to be injected as needed.
+ *
+ * This class also provides [.registerContentObserver] methods, normally found on [ContentResolver]
+ * instances, unifying setting related actions in one place.
+ */
+interface SettingsProxy {
+    /** Returns the [ContentResolver] this instance was constructed with. */
+    fun getContentResolver(): ContentResolver
+
+    /**
+     * Construct the content URI for a particular name/value pair, useful for monitoring changes
+     * with a ContentObserver.
+     *
+     * @param name to look up in the table
+     * @return the corresponding content URI, or null if not present
+     */
+    fun getUriFor(name: String): Uri
+
+    /**
+     * Convenience wrapper around [ContentResolver.registerContentObserver].'
+     *
+     * Implicitly calls [getUriFor] on the passed in name.
+     */
+    fun registerContentObserver(name: String, settingsObserver: ContentObserver) {
+        registerContentObserver(getUriFor(name), settingsObserver)
+    }
+
+    /** Convenience wrapper around [ContentResolver.registerContentObserver].' */
+    fun registerContentObserver(uri: Uri, settingsObserver: ContentObserver) =
+        registerContentObserver(uri, false, settingsObserver)
+
+    /**
+     * Convenience wrapper around [ContentResolver.registerContentObserver].'
+     *
+     * Implicitly calls [getUriFor] on the passed in name.
+     */
+    fun registerContentObserver(
+        name: String,
+        notifyForDescendants: Boolean,
+        settingsObserver: ContentObserver
+    ) = registerContentObserver(getUriFor(name), notifyForDescendants, settingsObserver)
+
+    /** Convenience wrapper around [ContentResolver.registerContentObserver].' */
+    fun registerContentObserver(
+        uri: Uri,
+        notifyForDescendants: Boolean,
+        settingsObserver: ContentObserver
+    ) = getContentResolver().registerContentObserver(uri, notifyForDescendants, settingsObserver)
+
+    /** See [ContentResolver.unregisterContentObserver]. */
+    fun unregisterContentObserver(settingsObserver: ContentObserver) =
+        getContentResolver().unregisterContentObserver(settingsObserver)
+
+    /**
+     * Look up a name in the database.
+     *
+     * @param name to look up in the table
+     * @return the corresponding value, or null if not present
+     */
+    fun getString(name: String): String
+
+    /**
+     * Store a name/value pair into the database.
+     *
+     * @param name to store
+     * @param value to associate with the name
+     * @return true if the value was set, false on database errors
+     */
+    fun putString(name: String, value: String): Boolean
+
+    /**
+     * Store a name/value pair into the database.
+     *
+     * The method takes an optional tag to associate with the setting which can be used to clear
+     * only settings made by your package and associated with this tag by passing the tag to
+     * [ ][.resetToDefaults]. Anyone can override the current tag. Also if another package changes
+     * the setting then the tag will be set to the one specified in the set call which can be null.
+     * Also any of the settings setters that do not take a tag as an argument effectively clears the
+     * tag.
+     *
+     * For example, if you set settings A and B with tags T1 and T2 and another app changes setting
+     * A (potentially to the same value), it can assign to it a tag T3 (note that now the package
+     * that changed the setting is not yours). Now if you reset your changes for T1 and T2 only
+     * setting B will be reset and A not (as it was changed by another package) but since A did not
+     * change you are in the desired initial state. Now if the other app changes the value of A
+     * (assuming you registered an observer in the beginning) you would detect that the setting was
+     * changed by another app and handle this appropriately (ignore, set back to some value, etc).
+     *
+     * Also the method takes an argument whether to make the value the default for this setting. If
+     * the system already specified a default value, then the one passed in here will **not** be set
+     * as the default.
+     *
+     * @param name to store.
+     * @param value to associate with the name.
+     * @param tag to associate with the setting.
+     * @param makeDefault whether to make the value the default one.
+     * @return true if the value was set, false on database errors.
+     * @see .resetToDefaults
+     */
+    fun putString(name: String, value: String, tag: String, makeDefault: Boolean): Boolean
+
+    /**
+     * Convenience function for retrieving a single secure settings value as an integer. Note that
+     * internally setting values are always stored as strings; this function converts the string to
+     * an integer for you. The default value will be returned if the setting is not defined or not
+     * an integer.
+     *
+     * @param name The name of the setting to retrieve.
+     * @param def Value to return if the setting is not defined.
+     * @return The setting's current value, or 'def' if it is not defined or not a valid integer.
+     */
+    fun getInt(name: String, def: Int): Int {
+        val v = getString(name)
+        return try {
+            v.toInt()
+        } catch (e: NumberFormatException) {
+            def
+        }
+    }
+
+    /**
+     * Convenience function for retrieving a single secure settings value as an integer. Note that
+     * internally setting values are always stored as strings; this function converts the string to
+     * an integer for you.
+     *
+     * This version does not take a default value. If the setting has not been set, or the string
+     * value is not a number, it throws [Settings.SettingNotFoundException].
+     *
+     * @param name The name of the setting to retrieve.
+     * @return The setting's current value.
+     * @throws Settings.SettingNotFoundException Thrown if a setting by the given name can't be
+     *   found or the setting value is not an integer.
+     */
+    @Throws(SettingNotFoundException::class)
+    fun getInt(name: String): Int {
+        val v = getString(name)
+        return try {
+            v.toInt()
+        } catch (e: NumberFormatException) {
+            throw SettingNotFoundException(name)
+        }
+    }
+
+    /**
+     * Convenience function for updating a single settings value as an integer. This will either
+     * create a new entry in the table if the given name does not exist, or modify the value of the
+     * existing row with that name. Note that internally setting values are always stored as
+     * strings, so this function converts the given value to a string before storing it.
+     *
+     * @param name The name of the setting to modify.
+     * @param value The new value for the setting.
+     * @return true if the value was set, false on database errors
+     */
+    fun putInt(name: String, value: Int): Boolean {
+        return putString(name, value.toString())
+    }
+
+    /**
+     * Convenience function for retrieving a single secure settings value as a boolean. Note that
+     * internally setting values are always stored as strings; this function converts the string to
+     * a boolean for you. The default value will be returned if the setting is not defined or not a
+     * boolean.
+     *
+     * @param name The name of the setting to retrieve.
+     * @param def Value to return if the setting is not defined.
+     * @return The setting's current value, or 'def' if it is not defined or not a valid boolean.
+     */
+    fun getBool(name: String, def: Boolean): Boolean {
+        return getInt(name, if (def) 1 else 0) != 0
+    }
+
+    /**
+     * Convenience function for retrieving a single secure settings value as a boolean. Note that
+     * internally setting values are always stored as strings; this function converts the string to
+     * a boolean for you.
+     *
+     * This version does not take a default value. If the setting has not been set, or the string
+     * value is not a number, it throws [Settings.SettingNotFoundException].
+     *
+     * @param name The name of the setting to retrieve.
+     * @return The setting's current value.
+     * @throws Settings.SettingNotFoundException Thrown if a setting by the given name can't be
+     *   found or the setting value is not a boolean.
+     */
+    @Throws(SettingNotFoundException::class)
+    fun getBool(name: String): Boolean {
+        return getInt(name) != 0
+    }
+
+    /**
+     * Convenience function for updating a single settings value as a boolean. This will either
+     * create a new entry in the table if the given name does not exist, or modify the value of the
+     * existing row with that name. Note that internally setting values are always stored as
+     * strings, so this function converts the given value to a string before storing it.
+     *
+     * @param name The name of the setting to modify.
+     * @param value The new value for the setting.
+     * @return true if the value was set, false on database errors
+     */
+    fun putBool(name: String, value: Boolean): Boolean {
+        return putInt(name, if (value) 1 else 0)
+    }
+
+    /**
+     * Convenience function for retrieving a single secure settings value as a `long`. Note that
+     * internally setting values are always stored as strings; this function converts the string to
+     * a `long` for you. The default value will be returned if the setting is not defined or not a
+     * `long`.
+     *
+     * @param name The name of the setting to retrieve.
+     * @param def Value to return if the setting is not defined.
+     * @return The setting's current value, or 'def' if it is not defined or not a valid `long`.
+     */
+    fun getLong(name: String, def: Long): Long {
+        val valString = getString(name)
+        return parseLongOrUseDefault(valString, def)
+    }
+
+    /**
+     * Convenience function for retrieving a single secure settings value as a `long`. Note that
+     * internally setting values are always stored as strings; this function converts the string to
+     * a `long` for you.
+     *
+     * This version does not take a default value. If the setting has not been set, or the string
+     * value is not a number, it throws [Settings.SettingNotFoundException].
+     *
+     * @param name The name of the setting to retrieve.
+     * @return The setting's current value.
+     * @throws Settings.SettingNotFoundException Thrown if a setting by the given name can't be
+     *   found or the setting value is not an integer.
+     */
+    @Throws(SettingNotFoundException::class)
+    fun getLong(name: String): Long {
+        val valString = getString(name)
+        return parseLongOrThrow(name, valString)
+    }
+
+    /**
+     * Convenience function for updating a secure settings value as a long integer. This will either
+     * create a new entry in the table if the given name does not exist, or modify the value of the
+     * existing row with that name. Note that internally setting values are always stored as
+     * strings, so this function converts the given value to a string before storing it.
+     *
+     * @param name The name of the setting to modify.
+     * @param value The new value for the setting.
+     * @return true if the value was set, false on database errors
+     */
+    fun putLong(name: String, value: Long): Boolean {
+        return putString(name, value.toString())
+    }
+
+    /**
+     * Convenience function for retrieving a single secure settings value as a floating point
+     * number. Note that internally setting values are always stored as strings; this function
+     * converts the string to an float for you. The default value will be returned if the setting is
+     * not defined or not a valid float.
+     *
+     * @param name The name of the setting to retrieve.
+     * @param def Value to return if the setting is not defined.
+     * @return The setting's current value, or 'def' if it is not defined or not a valid float.
+     */
+    fun getFloat(name: String, def: Float): Float {
+        val v = getString(name)
+        return parseFloat(v, def)
+    }
+
+    /**
+     * Convenience function for retrieving a single secure settings value as a float. Note that
+     * internally setting values are always stored as strings; this function converts the string to
+     * a float for you.
+     *
+     * This version does not take a default value. If the setting has not been set, or the string
+     * value is not a number, it throws [Settings.SettingNotFoundException].
+     *
+     * @param name The name of the setting to retrieve.
+     * @return The setting's current value.
+     * @throws Settings.SettingNotFoundException Thrown if a setting by the given name can't be
+     *   found or the setting value is not a float.
+     */
+    @Throws(SettingNotFoundException::class)
+    fun getFloat(name: String): Float {
+        val v = getString(name)
+        return parseFloatOrThrow(name, v)
+    }
+
+    /**
+     * Convenience function for updating a single settings value as a floating point number. This
+     * will either create a new entry in the table if the given name does not exist, or modify the
+     * value of the existing row with that name. Note that internally setting values are always
+     * stored as strings, so this function converts the given value to a string before storing it.
+     *
+     * @param name The name of the setting to modify.
+     * @param value The new value for the setting.
+     * @return true if the value was set, false on database errors
+     */
+    fun putFloat(name: String, value: Float): Boolean {
+        return putString(name, value.toString())
+    }
+
+    companion object {
+        /** Convert a string to a long, or uses a default if the string is malformed or null */
+        @JvmStatic
+        fun parseLongOrUseDefault(valString: String, def: Long): Long {
+            val value: Long
+            value =
+                try {
+                    valString.toLong()
+                } catch (e: NumberFormatException) {
+                    def
+                }
+            return value
+        }
+
+        /** Convert a string to a long, or throws an exception if the string is malformed or null */
+        @JvmStatic
+        @Throws(SettingNotFoundException::class)
+        fun parseLongOrThrow(name: String, valString: String?): Long {
+            if (valString == null) {
+                throw SettingNotFoundException(name)
+            }
+            return try {
+                valString.toLong()
+            } catch (e: NumberFormatException) {
+                throw SettingNotFoundException(name)
+            }
+        }
+
+        /** Convert a string to a float, or uses a default if the string is malformed or null */
+        @JvmStatic
+        fun parseFloat(v: String?, def: Float): Float {
+            return try {
+                v?.toFloat() ?: def
+            } catch (e: NumberFormatException) {
+                def
+            }
+        }
+
+        /**
+         * Convert a string to a float, or throws an exception if the string is malformed or null
+         */
+        @JvmStatic
+        @Throws(SettingNotFoundException::class)
+        fun parseFloatOrThrow(name: String, v: String?): Float {
+            if (v == null) {
+                throw SettingNotFoundException(name)
+            }
+            return try {
+                v.toFloat()
+            } catch (e: NumberFormatException) {
+                throw SettingNotFoundException(name)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java
deleted file mode 100644
index 10cf082..0000000
--- a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java
+++ /dev/null
@@ -1,283 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.util.settings;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.UserIdInt;
-import android.content.ContentResolver;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.UserHandle;
-import android.provider.Settings;
-
-import com.android.app.tracing.TraceUtils;
-import com.android.systemui.settings.UserTracker;
-
-import kotlin.Unit;
-
-/**
- * Used to interact with per-user Settings.Secure and Settings.System settings (but not
- * Settings.Global, since those do not vary per-user)
- * <p>
- * This interface can be implemented to give instance method (instead of static method) versions
- * of Settings.Secure and Settings.System. It can be injected into class constructors and then
- * faked or mocked as needed in tests.
- * <p>
- * You can ask for {@link SecureSettings} or {@link SystemSettings} to be injected as needed.
- * <p>
- * This class also provides {@link #registerContentObserver(String, ContentObserver)} methods,
- * normally found on {@link ContentResolver} instances, unifying setting related actions in one
- * place.
- */
-public interface UserSettingsProxy extends SettingsProxy {
-
-    /**
-     * Returns that {@link UserTracker} this instance was constructed with.
-     */
-    UserTracker getUserTracker();
-
-    /**
-     * Returns the user id for the associated {@link ContentResolver}.
-     */
-    default int getUserId() {
-        return getContentResolver().getUserId();
-    }
-
-    /**
-     * Returns the actual current user handle when querying with the current user. Otherwise,
-     * returns the passed in user id.
-     */
-    default int getRealUserHandle(int userHandle) {
-        if (userHandle != UserHandle.USER_CURRENT) {
-            return userHandle;
-        }
-        return getUserTracker().getUserId();
-    }
-
-    @Override
-    default void registerContentObserver(Uri uri, ContentObserver settingsObserver) {
-        registerContentObserverForUser(uri, settingsObserver, getUserId());
-    }
-
-    /**
-     * Convenience wrapper around
-     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.'
-     */
-    @Override
-    default void registerContentObserver(Uri uri, boolean notifyForDescendants,
-            ContentObserver settingsObserver) {
-        registerContentObserverForUser(uri, notifyForDescendants, settingsObserver, getUserId());
-    }
-
-    /**
-     * Convenience wrapper around
-     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
-     *
-     * Implicitly calls {@link #getUriFor(String)} on the passed in name.
-     */
-    default void registerContentObserverForUser(
-            String name, ContentObserver settingsObserver, int userHandle) {
-        registerContentObserverForUser(
-                getUriFor(name), settingsObserver, userHandle);
-    }
-
-    /**
-     * Convenience wrapper around
-     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
-     */
-    default void registerContentObserverForUser(
-            Uri uri, ContentObserver settingsObserver, int userHandle) {
-        registerContentObserverForUser(
-                uri, false, settingsObserver, userHandle);
-    }
-
-    /**
-     * Convenience wrapper around
-     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
-     *
-     * Implicitly calls {@link #getUriFor(String)} on the passed in name.
-     */
-    default void registerContentObserverForUser(
-            String name, boolean notifyForDescendants, ContentObserver settingsObserver,
-            int userHandle) {
-        registerContentObserverForUser(
-                getUriFor(name), notifyForDescendants, settingsObserver, userHandle);
-    }
-
-    /**
-     * Convenience wrapper around
-     * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)}
-     */
-    default void registerContentObserverForUser(
-            Uri uri, boolean notifyForDescendants, ContentObserver settingsObserver,
-            int userHandle) {
-        TraceUtils.trace(
-                () -> {
-                    // The limit for trace tags length is 127 chars, which leaves us 90 for Uri.
-                    return "USP#registerObserver#[" + uri.toString() + "]";
-                }, () -> {
-                    getContentResolver().registerContentObserver(
-                            uri, notifyForDescendants, settingsObserver,
-                            getRealUserHandle(userHandle));
-                    return Unit.INSTANCE;
-                });
-    }
-
-    /**
-     * Look up a name in the database.
-     * @param name to look up in the table
-     * @return the corresponding value, or null if not present
-     */
-    @Override
-    default String getString(String name) {
-        return getStringForUser(name, getUserId());
-    }
-
-    /**See {@link #getString(String)}. */
-    String getStringForUser(String name, int userHandle);
-
-    /**
-     * Store a name/value pair into the database. Values written by this method will be
-     * overridden if a restore happens in the future.
-     *
-     * @param name to store
-     * @param value to associate with the name
-     * @return true if the value was set, false on database errors
-     */
-    boolean putString(String name, String value, boolean overrideableByRestore);
-
-    @Override
-    default boolean putString(String name, String value) {
-        return putStringForUser(name, value, getUserId());
-    }
-
-    /** See {@link #putString(String, String)}. */
-    boolean putStringForUser(String name, String value, int userHandle);
-
-    /** See {@link #putString(String, String)}. */
-    boolean putStringForUser(@NonNull String name, @Nullable String value, @Nullable String tag,
-            boolean makeDefault, @UserIdInt int userHandle, boolean overrideableByRestore);
-
-    @Override
-    default int getInt(String name, int def) {
-        return getIntForUser(name, def, getUserId());
-    }
-
-    /** See {@link #getInt(String, int)}. */
-    default int getIntForUser(String name, int def, int userHandle) {
-        String v = getStringForUser(name, userHandle);
-        try {
-            return v != null ? Integer.parseInt(v) : def;
-        } catch (NumberFormatException e) {
-            return def;
-        }
-    }
-
-    @Override
-    default int getInt(String name) throws Settings.SettingNotFoundException {
-        return getIntForUser(name, getUserId());
-    }
-
-    /** See {@link #getInt(String)}. */
-    default int getIntForUser(String name, int userHandle)
-            throws Settings.SettingNotFoundException {
-        String v = getStringForUser(name, userHandle);
-        try {
-            return Integer.parseInt(v);
-        } catch (NumberFormatException e) {
-            throw new Settings.SettingNotFoundException(name);
-        }
-    }
-
-    @Override
-    default boolean putInt(String name, int value) {
-        return putIntForUser(name, value, getUserId());
-    }
-
-    /** See {@link #putInt(String, int)}. */
-    default boolean putIntForUser(String name, int value, int userHandle) {
-        return putStringForUser(name, Integer.toString(value), userHandle);
-    }
-
-    @Override
-    default boolean getBool(String name, boolean def) {
-        return getBoolForUser(name, def, getUserId());
-    }
-
-    /** See {@link #getBool(String, boolean)}. */
-    default boolean getBoolForUser(String name, boolean def, int userHandle) {
-        return getIntForUser(name, def ? 1 : 0, userHandle) != 0;
-    }
-
-    @Override
-    default boolean getBool(String name) throws Settings.SettingNotFoundException {
-        return getBoolForUser(name, getUserId());
-    }
-
-    /** See {@link #getBool(String)}. */
-    default boolean getBoolForUser(String name, int userHandle)
-            throws Settings.SettingNotFoundException {
-        return getIntForUser(name, userHandle) != 0;
-    }
-
-    @Override
-    default boolean putBool(String name, boolean value) {
-        return putBoolForUser(name, value, getUserId());
-    }
-
-    /** See {@link #putBool(String, boolean)}. */
-    default boolean putBoolForUser(String name, boolean value, int userHandle) {
-        return putIntForUser(name, value ? 1 : 0, userHandle);
-    }
-
-    /** See {@link #getLong(String, long)}. */
-    default long getLongForUser(String name, long def, int userHandle) {
-        String valString = getStringForUser(name, userHandle);
-        return SettingsProxy.parseLongOrUseDefault(valString, def);
-    }
-
-    /** See {@link #getLong(String)}. */
-    default long getLongForUser(String name, int userHandle)
-            throws Settings.SettingNotFoundException {
-        String valString = getStringForUser(name, userHandle);
-        return SettingsProxy.parseLongOrThrow(name, valString);
-    }
-
-    /** See {@link #putLong(String, long)}. */
-    default boolean putLongForUser(String name, long value, int userHandle) {
-        return putStringForUser(name, Long.toString(value), userHandle);
-    }
-
-    /** See {@link #getFloat(String)}. */
-    default float getFloatForUser(String name, float def, int userHandle) {
-        String v = getStringForUser(name, userHandle);
-        return SettingsProxy.parseFloat(v, def);
-    }
-
-    /** See {@link #getFloat(String, float)}. */
-    default float getFloatForUser(String name, int userHandle)
-            throws Settings.SettingNotFoundException {
-        String v = getStringForUser(name, userHandle);
-        return SettingsProxy.parseFloatOrThrow(name, v);
-    }
-
-    /** See {@link #putFloat(String, float)} */
-    default boolean putFloatForUser(String name, float value, int userHandle) {
-        return putStringForUser(name, Float.toString(value), userHandle);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
new file mode 100644
index 0000000..2285270
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
@@ -0,0 +1,269 @@
+/*
+ * 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.settings
+
+import android.annotation.UserIdInt
+import android.database.ContentObserver
+import android.net.Uri
+import android.os.UserHandle
+import android.provider.Settings.SettingNotFoundException
+import com.android.app.tracing.TraceUtils.trace
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.settings.SettingsProxy.Companion.parseFloat
+import com.android.systemui.util.settings.SettingsProxy.Companion.parseFloatOrThrow
+import com.android.systemui.util.settings.SettingsProxy.Companion.parseLongOrThrow
+import com.android.systemui.util.settings.SettingsProxy.Companion.parseLongOrUseDefault
+
+/**
+ * Used to interact with per-user Settings.Secure and Settings.System settings (but not
+ * Settings.Global, since those do not vary per-user)
+ *
+ * This interface can be implemented to give instance method (instead of static method) versions of
+ * Settings.Secure and Settings.System. It can be injected into class constructors and then faked or
+ * mocked as needed in tests.
+ *
+ * You can ask for [SecureSettings] or [SystemSettings] to be injected as needed.
+ *
+ * This class also provides [.registerContentObserver] methods, normally found on [ContentResolver]
+ * instances, unifying setting related actions in one place.
+ */
+interface UserSettingsProxy : SettingsProxy {
+
+    /** Returns that [UserTracker] this instance was constructed with. */
+    val userTracker: UserTracker
+
+    /** Returns the user id for the associated [ContentResolver]. */
+    var userId: Int
+        get() = getContentResolver().userId
+        set(_) {
+            throw UnsupportedOperationException(
+                "userId cannot be set in interface, use setter from an implementation instead."
+            )
+        }
+
+    /**
+     * Returns the actual current user handle when querying with the current user. Otherwise,
+     * returns the passed in user id.
+     */
+    fun getRealUserHandle(userHandle: Int): Int {
+        return if (userHandle != UserHandle.USER_CURRENT) {
+            userHandle
+        } else userTracker.userId
+    }
+
+    override fun registerContentObserver(uri: Uri, settingsObserver: ContentObserver) {
+        registerContentObserverForUser(uri, settingsObserver, userId)
+    }
+
+    /** Convenience wrapper around [ContentResolver.registerContentObserver].' */
+    override fun registerContentObserver(
+        uri: Uri,
+        notifyForDescendants: Boolean,
+        settingsObserver: ContentObserver
+    ) {
+        registerContentObserverForUser(uri, notifyForDescendants, settingsObserver, userId)
+    }
+
+    /**
+     * Convenience wrapper around [ContentResolver.registerContentObserver]
+     *
+     * Implicitly calls [getUriFor] on the passed in name.
+     */
+    fun registerContentObserverForUser(
+        name: String,
+        settingsObserver: ContentObserver,
+        userHandle: Int
+    ) {
+        registerContentObserverForUser(getUriFor(name), settingsObserver, userHandle)
+    }
+
+    /** Convenience wrapper around [ContentResolver.registerContentObserver] */
+    fun registerContentObserverForUser(
+        uri: Uri,
+        settingsObserver: ContentObserver,
+        userHandle: Int
+    ) {
+        registerContentObserverForUser(uri, false, settingsObserver, userHandle)
+    }
+
+    /**
+     * Convenience wrapper around [ContentResolver.registerContentObserver]
+     *
+     * Implicitly calls [getUriFor] on the passed in name.
+     */
+    fun registerContentObserverForUser(
+        name: String,
+        notifyForDescendants: Boolean,
+        settingsObserver: ContentObserver,
+        userHandle: Int
+    ) {
+        registerContentObserverForUser(
+            getUriFor(name),
+            notifyForDescendants,
+            settingsObserver,
+            userHandle
+        )
+    }
+
+    /** Convenience wrapper around [ContentResolver.registerContentObserver] */
+    fun registerContentObserverForUser(
+        uri: Uri,
+        notifyForDescendants: Boolean,
+        settingsObserver: ContentObserver,
+        userHandle: Int
+    ) {
+        trace({ "USP#registerObserver#[$uri]" }) {
+            getContentResolver()
+                .registerContentObserver(
+                    uri,
+                    notifyForDescendants,
+                    settingsObserver,
+                    getRealUserHandle(userHandle)
+                )
+            Unit
+        }
+    }
+
+    /**
+     * Look up a name in the database.
+     *
+     * @param name to look up in the table
+     * @return the corresponding value, or null if not present
+     */
+    override fun getString(name: String): String {
+        return getStringForUser(name, userId)
+    }
+
+    /** See [getString]. */
+    fun getStringForUser(name: String, userHandle: Int): String
+
+    /**
+     * Store a name/value pair into the database. Values written by this method will be overridden
+     * if a restore happens in the future.
+     *
+     * @param name to store
+     * @param value to associate with the name
+     * @return true if the value was set, false on database errors
+     */
+    fun putString(name: String, value: String, overrideableByRestore: Boolean): Boolean
+    override fun putString(name: String, value: String): Boolean {
+        return putStringForUser(name, value, userId)
+    }
+
+    /** Similar implementation to [putString] for the specified [userHandle]. */
+    fun putStringForUser(name: String, value: String, userHandle: Int): Boolean
+
+    /** Similar implementation to [putString] for the specified [userHandle]. */
+    fun putStringForUser(
+        name: String,
+        value: String,
+        tag: String?,
+        makeDefault: Boolean,
+        @UserIdInt userHandle: Int,
+        overrideableByRestore: Boolean
+    ): Boolean
+
+    override fun getInt(name: String, def: Int): Int {
+        return getIntForUser(name, def, userId)
+    }
+
+    /** Similar implementation to [getInt] for the specified [userHandle]. */
+    fun getIntForUser(name: String, def: Int, userHandle: Int): Int {
+        val v = getStringForUser(name, userHandle)
+        return try {
+            v.toInt()
+        } catch (e: NumberFormatException) {
+            def
+        }
+    }
+
+    @Throws(SettingNotFoundException::class)
+    override fun getInt(name: String) = getIntForUser(name, userId)
+
+    /** Similar implementation to [getInt] for the specified [userHandle]. */
+    @Throws(SettingNotFoundException::class)
+    fun getIntForUser(name: String, userHandle: Int): Int {
+        val v = getStringForUser(name, userHandle)
+        return try {
+            v.toInt()
+        } catch (e: NumberFormatException) {
+            throw SettingNotFoundException(name)
+        }
+    }
+
+    override fun putInt(name: String, value: Int) = putIntForUser(name, value, userId)
+
+    /** Similar implementation to [getInt] for the specified [userHandle]. */
+    fun putIntForUser(name: String, value: Int, userHandle: Int) =
+        putStringForUser(name, value.toString(), userHandle)
+
+    override fun getBool(name: String, def: Boolean) = getBoolForUser(name, def, userId)
+
+    /** Similar implementation to [getBool] for the specified [userHandle]. */
+    fun getBoolForUser(name: String, def: Boolean, userHandle: Int) =
+        getIntForUser(name, if (def) 1 else 0, userHandle) != 0
+
+    @Throws(SettingNotFoundException::class)
+    override fun getBool(name: String) = getBoolForUser(name, userId)
+
+    /** Similar implementation to [getBool] for the specified [userHandle]. */
+    @Throws(SettingNotFoundException::class)
+    fun getBoolForUser(name: String, userHandle: Int): Boolean {
+        return getIntForUser(name, userHandle) != 0
+    }
+
+    override fun putBool(name: String, value: Boolean): Boolean {
+        return putBoolForUser(name, value, userId)
+    }
+
+    /** Similar implementation to [putBool] for the specified [userHandle]. */
+    fun putBoolForUser(name: String, value: Boolean, userHandle: Int) =
+        putIntForUser(name, if (value) 1 else 0, userHandle)
+
+    /** Similar implementation to [getLong] for the specified [userHandle]. */
+    fun getLongForUser(name: String, def: Long, userHandle: Int): Long {
+        val valString = getStringForUser(name, userHandle)
+        return parseLongOrUseDefault(valString, def)
+    }
+
+    /** Similar implementation to [getLong] for the specified [userHandle]. */
+    @Throws(SettingNotFoundException::class)
+    fun getLongForUser(name: String, userHandle: Int): Long {
+        val valString = getStringForUser(name, userHandle)
+        return parseLongOrThrow(name, valString)
+    }
+
+    /** Similar implementation to [putLong] for the specified [userHandle]. */
+    fun putLongForUser(name: String, value: Long, userHandle: Int) =
+        putStringForUser(name, value.toString(), userHandle)
+
+    /** Similar implementation to [getFloat] for the specified [userHandle]. */
+    fun getFloatForUser(name: String, def: Float, userHandle: Int): Float {
+        val v = getStringForUser(name, userHandle)
+        return parseFloat(v, def)
+    }
+
+    /** Similar implementation to [getFloat] for the specified [userHandle]. */
+    @Throws(SettingNotFoundException::class)
+    fun getFloatForUser(name: String, userHandle: Int): Float {
+        val v = getStringForUser(name, userHandle)
+        return parseFloatOrThrow(name, v)
+    }
+
+    /** Similar implementation to [putFloat] for the specified [userHandle]. */
+    fun putFloatForUser(name: String, value: Float, userHandle: Int) =
+        putStringForUser(name, value.toString(), userHandle)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt
index 4812765..a714f80 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt
@@ -86,7 +86,7 @@
         send(MediaControllerChangeModel.ExtrasChanged(extras))
     }
 
-    override fun onAudioInfoChanged(info: MediaController.PlaybackInfo?) {
+    override fun onAudioInfoChanged(info: MediaController.PlaybackInfo) {
         send(MediaControllerChangeModel.AudioInfoChanged(info))
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt
index 599bd73..6e1ebc8 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt
@@ -57,7 +57,7 @@
     }
 
     /** [MediaController.PlaybackInfo] changes for the [MediaDeviceSession]. */
-    fun playbackInfo(session: MediaDeviceSession): Flow<MediaController.PlaybackInfo?> {
+    fun playbackInfo(session: MediaDeviceSession): Flow<MediaController.PlaybackInfo> {
         return stateChanges(session) {
                 emit(MediaControllerChangeModel.AudioInfoChanged(it.playbackInfo))
             }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaControllerChangeModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaControllerChangeModel.kt
index ef5a44a..8b5116a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaControllerChangeModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaControllerChangeModel.kt
@@ -40,6 +40,6 @@
 
     data class ExtrasChanged(val extras: Bundle?) : MediaControllerChangeModel
 
-    data class AudioInfoChanged(val info: MediaController.PlaybackInfo?) :
+    data class AudioInfoChanged(val info: MediaController.PlaybackInfo) :
         MediaControllerChangeModel
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt
index 0df4fbf..9ba56d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt
@@ -36,12 +36,12 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
-import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.any
 
 private const val USER_ID = 8
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
index 7856f9b..a89139b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
@@ -196,7 +196,7 @@
         verify(globalSettings)
             .registerContentObserver(
                 eq(Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE)),
-                settingsObserverCaptor.capture()
+                capture(settingsObserverCaptor)
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index 9bb21f0..9616f610 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -128,6 +128,7 @@
                         mContext,
                         TEST_PACKAGE,
                         mContext.getUser(),
+                        /* token */ null,
                         mMediaSessionManager,
                         mLocalBluetoothManager,
                         mStarter,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
index 2e6388a..16b00c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
@@ -129,6 +129,7 @@
                         mContext,
                         TEST_PACKAGE,
                         mContext.getUser(),
+                        /* token */ null,
                         mMediaSessionManager,
                         mLocalBluetoothManager,
                         mStarter,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index 4eb0038..45ae506 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -199,6 +199,7 @@
                         mSpyContext,
                         mPackageName,
                         mContext.getUser(),
+                        /* token */ null,
                         mMediaSessionManager,
                         mLocalBluetoothManager,
                         mStarter,
@@ -292,6 +293,7 @@
                         mSpyContext,
                         null,
                         mContext.getUser(),
+                        /* token */ null,
                         mMediaSessionManager,
                         mLocalBluetoothManager,
                         mStarter,
@@ -333,6 +335,7 @@
                         mSpyContext,
                         null,
                         mSpyContext.getUser(),
+                        /* token */ null,
                         mMediaSessionManager,
                         mLocalBluetoothManager,
                         mStarter,
@@ -588,6 +591,7 @@
                         mSpyContext,
                         "",
                         mSpyContext.getUser(),
+                        /* token */ null,
                         mMediaSessionManager,
                         mLocalBluetoothManager,
                         mStarter,
@@ -621,6 +625,7 @@
                         mSpyContext,
                         "",
                         mSpyContext.getUser(),
+                        /* token */ null,
                         mMediaSessionManager,
                         mLocalBluetoothManager,
                         mStarter,
@@ -667,6 +672,7 @@
                         mSpyContext,
                         null,
                         mSpyContext.getUser(),
+                        /* token */ null,
                         mMediaSessionManager,
                         mLocalBluetoothManager,
                         mStarter,
@@ -693,6 +699,7 @@
                         mSpyContext,
                         null,
                         mSpyContext.getUser(),
+                        /* token */ null,
                         mMediaSessionManager,
                         mLocalBluetoothManager,
                         mStarter,
@@ -972,6 +979,7 @@
                         mSpyContext,
                         null,
                         mSpyContext.getUser(),
+                        /* token */ null,
                         mMediaSessionManager,
                         mLocalBluetoothManager,
                         mStarter,
@@ -1174,6 +1182,7 @@
                         mSpyContext,
                         null,
                         mSpyContext.getUser(),
+                        /* token */ null,
                         mMediaSessionManager,
                         mLocalBluetoothManager,
                         mStarter,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
index 5dbfe47..1e8fbea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
@@ -64,7 +64,7 @@
         mMediaOutputDialogReceiver.onReceive(getContext(), intent);
 
         verify(mMockMediaOutputDialogManager, times(1))
-                .createAndShow(eq(getContext().getPackageName()), eq(false), any(), any());
+                .createAndShow(eq(getContext().getPackageName()), eq(false), any(), any(), any());
         verify(mMockMediaOutputBroadcastDialogManager, never())
                 .createAndShow(any(), anyBoolean(), any());
     }
@@ -76,7 +76,7 @@
         mMediaOutputDialogReceiver.onReceive(getContext(), intent);
 
         verify(mMockMediaOutputDialogManager, never())
-                .createAndShow(any(), anyBoolean(), any(), any());
+                .createAndShow(any(), anyBoolean(), any(), any(), any());
         verify(mMockMediaOutputBroadcastDialogManager, never())
                 .createAndShow(any(), anyBoolean(), any());
     }
@@ -87,7 +87,7 @@
         mMediaOutputDialogReceiver.onReceive(getContext(), intent);
 
         verify(mMockMediaOutputDialogManager, never())
-                .createAndShow(any(), anyBoolean(), any(), any());
+                .createAndShow(any(), anyBoolean(), any(), any(), any());
         verify(mMockMediaOutputBroadcastDialogManager, never())
                 .createAndShow(any(), anyBoolean(), any());
     }
@@ -101,7 +101,7 @@
         mMediaOutputDialogReceiver.onReceive(getContext(), intent);
 
         verify(mMockMediaOutputDialogManager, never())
-                .createAndShow(any(), anyBoolean(), any(), any());
+                .createAndShow(any(), anyBoolean(), any(), any(), any());
         verify(mMockMediaOutputBroadcastDialogManager, never())
                 .createAndShow(any(), anyBoolean(), any());
     }
@@ -115,7 +115,7 @@
         mMediaOutputDialogReceiver.onReceive(getContext(), intent);
 
         verify(mMockMediaOutputDialogManager, never())
-                .createAndShow(any(), anyBoolean(), any(), any());
+                .createAndShow(any(), anyBoolean(), any(), any(), any());
         verify(mMockMediaOutputBroadcastDialogManager, times(1))
                 .createAndShow(eq(getContext().getPackageName()), eq(true), any());
     }
@@ -129,7 +129,7 @@
         mMediaOutputDialogReceiver.onReceive(getContext(), intent);
 
         verify(mMockMediaOutputDialogManager, never())
-                .createAndShow(any(), anyBoolean(), any(), any());
+                .createAndShow(any(), anyBoolean(), any(), any(), any());
         verify(mMockMediaOutputBroadcastDialogManager, never())
                 .createAndShow(any(), anyBoolean(), any());
     }
@@ -142,7 +142,7 @@
         mMediaOutputDialogReceiver.onReceive(getContext(), intent);
 
         verify(mMockMediaOutputDialogManager, never())
-                .createAndShow(any(), anyBoolean(), any(), any());
+                .createAndShow(any(), anyBoolean(), any(), any(), any());
         verify(mMockMediaOutputBroadcastDialogManager, never())
                 .createAndShow(any(), anyBoolean(), any());
     }
@@ -155,7 +155,7 @@
         mMediaOutputDialogReceiver.onReceive(getContext(), intent);
 
         verify(mMockMediaOutputDialogManager, never())
-                .createAndShow(any(), anyBoolean(), any(), any());
+                .createAndShow(any(), anyBoolean(), any(), any(), any());
         verify(mMockMediaOutputBroadcastDialogManager, never())
                 .createAndShow(any(), anyBoolean(), any());
     }
@@ -166,7 +166,7 @@
         mMediaOutputDialogReceiver.onReceive(getContext(), intent);
 
         verify(mMockMediaOutputDialogManager, never())
-                .createAndShow(any(), anyBoolean(), any(), any());
+                .createAndShow(any(), anyBoolean(), any(), any(), any());
         verify(mMockMediaOutputBroadcastDialogManager, never())
                 .createAndShow(any(), anyBoolean(), any());
     }
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 cdef964..92d0a72 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
@@ -142,6 +142,7 @@
                         mContext,
                         TEST_PACKAGE,
                         mContext.getUser(),
+                        /* token */ null,
                         mMediaSessionManager,
                         mLocalBluetoothManager,
                         mStarter,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 4d32cc4..043dba1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -24,6 +24,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+import static kotlinx.coroutines.flow.SharedFlowKt.MutableSharedFlow;
 import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -204,6 +205,7 @@
 import dagger.Lazy;
 
 import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.channels.BufferOverflow;
 import kotlinx.coroutines.test.TestScope;
 
 import org.junit.After;
@@ -351,7 +353,6 @@
     @Mock private KeyguardClockPositionAlgorithm mKeyguardClockPositionAlgorithm;
     @Mock private NaturalScrollingSettingObserver mNaturalScrollingSettingObserver;
     @Mock private LargeScreenHeaderHelper mLargeScreenHeaderHelper;
-
     protected final int mMaxUdfpsBurnInOffsetY = 5;
     protected FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic();
     protected KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
@@ -366,8 +367,11 @@
     protected PowerInteractor mPowerInteractor;
     protected FakeHeadsUpNotificationRepository mFakeHeadsUpNotificationRepository =
             new FakeHeadsUpNotificationRepository();
-    protected HeadsUpNotificationInteractor mHeadsUpNotificationInteractor =
-            new HeadsUpNotificationInteractor(mFakeHeadsUpNotificationRepository);
+    protected NotificationsKeyguardViewStateRepository mNotificationsKeyguardViewStateRepository =
+            new NotificationsKeyguardViewStateRepository();
+    protected NotificationsKeyguardInteractor mNotificationsKeyguardInteractor =
+            new NotificationsKeyguardInteractor(mNotificationsKeyguardViewStateRepository);
+    protected HeadsUpNotificationInteractor mHeadsUpNotificationInteractor;
     protected NotificationPanelViewController.TouchHandler mTouchHandler;
     protected ConfigurationController mConfigurationController;
     protected SysuiStatusBarStateController mStatusBarStateController;
@@ -417,6 +421,9 @@
         mPowerInteractor = keyguardInteractorDeps.getPowerInteractor();
         when(mKeyguardTransitionInteractor.isInTransitionToStateWhere(any())).thenReturn(
                 MutableStateFlow(false));
+        when(mKeyguardTransitionInteractor.getCurrentKeyguardState()).thenReturn(
+                MutableSharedFlow(0, 0, BufferOverflow.SUSPEND));
+        when(mDeviceEntryFaceAuthInteractor.isBypassEnabled()).thenReturn(MutableStateFlow(false));
         DeviceEntryUdfpsInteractor deviceEntryUdfpsInteractor =
                 mock(DeviceEntryUdfpsInteractor.class);
         when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(MutableStateFlow(false));
@@ -670,6 +677,11 @@
         when(mView.requireViewById(R.id.keyguard_long_press))
                 .thenReturn(mock(LongPressHandlingView.class));
 
+        mHeadsUpNotificationInteractor =
+                new HeadsUpNotificationInteractor(mFakeHeadsUpNotificationRepository,
+                        mDeviceEntryFaceAuthInteractor, mKeyguardTransitionInteractor,
+                        mNotificationsKeyguardInteractor, mShadeInteractor);
+
         mNotificationPanelViewController = new NotificationPanelViewController(
                 mView,
                 mMainHandler,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
index 6631d29..e1ee358 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
@@ -251,7 +251,7 @@
 
             // WHEN a pinned heads up is present
             mFakeHeadsUpNotificationRepository.setNotifications(
-                fakeHeadsUpRowRepository("key", isPinned = true)
+                FakeHeadsUpRowRepository("key", isPinned = true)
             )
         }
         advanceUntilIdle()
@@ -274,9 +274,4 @@
         // THEN the panel should be visible
         assertThat(mNotificationPanelViewController.isExpanded).isTrue()
     }
-
-    private fun fakeHeadsUpRowRepository(key: String, isPinned: Boolean = false) =
-        FakeHeadsUpRowRepository(key = key, elementKey = Any()).apply {
-            this.isPinned.value = isPinned
-        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index 71f09a5..f3d6407 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -181,6 +181,7 @@
         mViewModel =
                 new KeyguardStatusBarViewModel(
                         mTestScope.getBackgroundScope(),
+                        mKosmos.getHeadsUpNotificationInteractor(),
                         mKeyguardInteractor,
                         new KeyguardStatusBarInteractor(new FakeKeyguardStatusBarRepository()),
                         mBatteryController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
index ab10bc4..d88289d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
@@ -21,12 +21,18 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.andSceneContainer
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.statusbar.domain.interactor.keyguardStatusBarInteractor
+import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
+import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
+import com.android.systemui.statusbar.notification.stack.data.repository.setNotifications
+import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.statusbar.policy.batteryController
 import com.android.systemui.testKosmos
@@ -50,7 +56,11 @@
 class KeyguardStatusBarViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
+    private val faceAuthRepository by lazy { kosmos.fakeDeviceEntryFaceAuthRepository }
+    private val headsUpRepository by lazy { kosmos.headsUpNotificationRepository }
+    private val headsUpNotificationInteractor by lazy { kosmos.headsUpNotificationInteractor }
     private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
+    private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
     private val keyguardInteractor by lazy { kosmos.keyguardInteractor }
     private val keyguardStatusBarInteractor by lazy { kosmos.keyguardStatusBarInteractor }
     private val batteryController = kosmos.batteryController
@@ -74,6 +84,7 @@
         underTest =
             KeyguardStatusBarViewModel(
                 testScope.backgroundScope,
+                headsUpNotificationInteractor,
                 keyguardInteractor,
                 keyguardStatusBarInteractor,
                 batteryController,
@@ -112,7 +123,22 @@
         }
 
     @Test
-    fun isVisible_statusBarStateKeyguard_andNotDozing_true() =
+    fun isVisible_headsUpStatusBarShown_false() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isVisible)
+
+            // WHEN HUN displayed on the bypass lock screen
+            headsUpRepository.setNotifications(FakeHeadsUpRowRepository("key 0", isPinned = true))
+            keyguardTransitionRepository.emitInitialStepsFromOff(KeyguardState.LOCKSCREEN)
+            keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+            faceAuthRepository.isBypassEnabled.value = true
+
+            // THEN KeyguardStatusBar is NOT visible to make space for HeadsUpStatusBar
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    fun isVisible_statusBarStateKeyguard_andNotDozing_andNotShowingHeadsUpStatusBar_true() =
         testScope.runTest {
             val latest by collectLastValue(underTest.isVisible)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt
new file mode 100644
index 0000000..ab95707
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.settings
+
+import android.content.ContentResolver
+import android.database.ContentObserver
+import android.net.Uri
+import android.os.Handler
+import android.os.Looper
+import android.provider.Settings.SettingNotFoundException
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.eq
+
+/** Tests for [SettingsProxy]. */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@TestableLooper.RunWithLooper
+class SettingsProxyTest : SysuiTestCase() {
+
+    private lateinit var mSettings: SettingsProxy
+    private lateinit var mContentObserver: ContentObserver
+
+    @Before
+    fun setUp() {
+        mSettings = FakeSettingsProxy()
+        mContentObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {}
+    }
+
+    @Test
+    fun registerContentObserver_inputString_success() {
+        mSettings.registerContentObserver(TEST_SETTING, mContentObserver)
+        verify(mSettings.getContentResolver())
+            .registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver))
+    }
+
+    @Test
+    fun registerContentObserver_inputString_notifyForDescendants_true() {
+        mSettings.registerContentObserver(
+            TEST_SETTING,
+            notifyForDescendants = true,
+            mContentObserver
+        )
+        verify(mSettings.getContentResolver())
+            .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver))
+    }
+
+    @Test
+    fun registerContentObserver_inputUri_success() {
+        mSettings.registerContentObserver(TEST_SETTING_URI, mContentObserver)
+        verify(mSettings.getContentResolver())
+            .registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver))
+    }
+
+    @Test
+    fun registerContentObserver_inputUri_notifyForDescendants_true() {
+        mSettings.registerContentObserver(
+            TEST_SETTING_URI,
+            notifyForDescendants = true,
+            mContentObserver
+        )
+        verify(mSettings.getContentResolver())
+            .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver))
+    }
+
+    @Test
+    fun unregisterContentObserver() {
+        mSettings.unregisterContentObserver(mContentObserver)
+        verify(mSettings.getContentResolver()).unregisterContentObserver(eq(mContentObserver))
+    }
+
+    @Test
+    fun getString_keyPresent_returnValidValue() {
+        mSettings.putString(TEST_SETTING, "test")
+        assertThat(mSettings.getString(TEST_SETTING)).isEqualTo("test")
+    }
+
+    @Test
+    fun getString_keyAbsent_returnEmptyValue() {
+        assertThat(mSettings.getString(TEST_SETTING)).isEmpty()
+    }
+
+    @Test
+    fun getInt_keyPresent_returnValidValue() {
+        mSettings.putInt(TEST_SETTING, 2)
+        assertThat(mSettings.getInt(TEST_SETTING)).isEqualTo(2)
+    }
+
+    @Test
+    fun getInt_keyPresent_nonIntegerValue_throwException() {
+        assertThrows(SettingNotFoundException::class.java) {
+            mSettings.putString(TEST_SETTING, "test")
+            mSettings.getInt(TEST_SETTING)
+        }
+    }
+
+    @Test
+    fun getInt_keyAbsent_throwException() {
+        assertThrows(SettingNotFoundException::class.java) { mSettings.getInt(TEST_SETTING) }
+    }
+
+    @Test
+    fun getInt_keyAbsent_returnDefaultValue() {
+        assertThat(mSettings.getInt(TEST_SETTING, 5)).isEqualTo(5)
+    }
+
+    @Test
+    fun getBool_keyPresent_returnValidValue() {
+        mSettings.putBool(TEST_SETTING, true)
+        assertThat(mSettings.getBool(TEST_SETTING)).isTrue()
+    }
+
+    @Test
+    fun getBool_keyPresent_nonBooleanValue_throwException() {
+        assertThrows(SettingNotFoundException::class.java) {
+            mSettings.putString(TEST_SETTING, "test")
+            mSettings.getBool(TEST_SETTING)
+        }
+    }
+
+    @Test
+    fun getBool_keyAbsent_throwException() {
+        assertThrows(SettingNotFoundException::class.java) { mSettings.getBool(TEST_SETTING) }
+    }
+
+    @Test
+    fun getBool_keyAbsent_returnDefaultValue() {
+        assertThat(mSettings.getBool(TEST_SETTING, false)).isEqualTo(false)
+    }
+
+    @Test
+    fun getLong_keyPresent_returnValidValue() {
+        mSettings.putLong(TEST_SETTING, 1L)
+        assertThat(mSettings.getLong(TEST_SETTING)).isEqualTo(1L)
+    }
+
+    @Test
+    fun getLong_keyPresent_nonLongValue_throwException() {
+        assertThrows(SettingNotFoundException::class.java) {
+            mSettings.putString(TEST_SETTING, "test")
+            mSettings.getLong(TEST_SETTING)
+        }
+    }
+
+    @Test
+    fun getLong_keyAbsent_throwException() {
+        assertThrows(SettingNotFoundException::class.java) { mSettings.getLong(TEST_SETTING) }
+    }
+
+    @Test
+    fun getLong_keyAbsent_returnDefaultValue() {
+        assertThat(mSettings.getLong(TEST_SETTING, 2L)).isEqualTo(2L)
+    }
+
+    @Test
+    fun getFloat_keyPresent_returnValidValue() {
+        mSettings.putFloat(TEST_SETTING, 2.5F)
+        assertThat(mSettings.getFloat(TEST_SETTING)).isEqualTo(2.5F)
+    }
+
+    @Test
+    fun getFloat_keyPresent_nonFloatValue_throwException() {
+        assertThrows(SettingNotFoundException::class.java) {
+            mSettings.putString(TEST_SETTING, "test")
+            mSettings.getFloat(TEST_SETTING)
+        }
+    }
+
+    @Test
+    fun getFloat_keyAbsent_throwException() {
+        assertThrows(SettingNotFoundException::class.java) { mSettings.getFloat(TEST_SETTING) }
+    }
+
+    @Test
+    fun getFloat_keyAbsent_returnDefaultValue() {
+        assertThat(mSettings.getFloat(TEST_SETTING, 2.5F)).isEqualTo(2.5F)
+    }
+
+    private class FakeSettingsProxy : SettingsProxy {
+
+        private val mContentResolver = mock(ContentResolver::class.java)
+        private val settingToValueMap: MutableMap<String, String> = mutableMapOf()
+
+        override fun getContentResolver() = mContentResolver
+
+        override fun getUriFor(name: String) =
+            Uri.parse(StringBuilder().append("content://settings/").append(name).toString())
+
+        override fun getString(name: String): String {
+            return settingToValueMap[name] ?: ""
+        }
+
+        override fun putString(name: String, value: String): Boolean {
+            settingToValueMap[name] = value
+            return true
+        }
+
+        override fun putString(
+            name: String,
+            value: String,
+            tag: String,
+            makeDefault: Boolean
+        ): Boolean {
+            settingToValueMap[name] = value
+            return true
+        }
+    }
+
+    companion object {
+        private const val TEST_SETTING = "test_setting"
+        private val TEST_SETTING_URI = Uri.parse("content://settings/test_setting")
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
new file mode 100644
index 0000000..56328b9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.settings
+
+import android.content.ContentResolver
+import android.content.pm.UserInfo
+import android.database.ContentObserver
+import android.net.Uri
+import android.os.Handler
+import android.os.Looper
+import android.provider.Settings
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.settings.UserTracker
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.eq
+
+/** Tests for [UserSettingsProxy]. */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@TestableLooper.RunWithLooper
+class UserSettingsProxyTest : SysuiTestCase() {
+
+    private var mUserTracker = FakeUserTracker()
+    private var mSettings: UserSettingsProxy = FakeUserSettingsProxy(mUserTracker)
+    private var mContentObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {}
+
+    @Before
+    fun setUp() {
+        mUserTracker.set(
+            listOf(UserInfo(MAIN_USER_ID, "main", UserInfo.FLAG_MAIN)),
+            selectedUserIndex = 0
+        )
+    }
+
+    @Test
+    fun registerContentObserverForUser_inputString_success() {
+        mSettings.registerContentObserverForUser(
+            TEST_SETTING,
+            mContentObserver,
+            mUserTracker.userId
+        )
+        verify(mSettings.getContentResolver())
+            .registerContentObserver(
+                eq(TEST_SETTING_URI),
+                eq(false),
+                eq(mContentObserver),
+                eq(MAIN_USER_ID)
+            )
+    }
+
+    @Test
+    fun registerContentObserverForUser_inputString_notifyForDescendants_true() {
+        mSettings.registerContentObserverForUser(
+            TEST_SETTING,
+            notifyForDescendants = true,
+            mContentObserver,
+            mUserTracker.userId
+        )
+        verify(mSettings.getContentResolver())
+            .registerContentObserver(
+                eq(TEST_SETTING_URI),
+                eq(true),
+                eq(mContentObserver),
+                eq(MAIN_USER_ID)
+            )
+    }
+
+    @Test
+    fun registerContentObserverForUser_inputUri_success() {
+        mSettings.registerContentObserverForUser(
+            TEST_SETTING_URI,
+            mContentObserver,
+            mUserTracker.userId
+        )
+        verify(mSettings.getContentResolver())
+            .registerContentObserver(
+                eq(TEST_SETTING_URI),
+                eq(false),
+                eq(mContentObserver),
+                eq(MAIN_USER_ID)
+            )
+    }
+
+    @Test
+    fun registerContentObserverForUser_inputUri_notifyForDescendants_true() {
+        mSettings.registerContentObserverForUser(
+            TEST_SETTING_URI,
+            notifyForDescendants = true,
+            mContentObserver,
+            mUserTracker.userId
+        )
+        verify(mSettings.getContentResolver())
+            .registerContentObserver(
+                eq(TEST_SETTING_URI),
+                eq(true),
+                eq(mContentObserver),
+                eq(MAIN_USER_ID)
+            )
+    }
+
+    @Test
+    fun registerContentObserver_inputUri_success() {
+        mSettings.registerContentObserver(TEST_SETTING_URI, mContentObserver)
+        verify(mSettings.getContentResolver())
+            .registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver), eq(0))
+    }
+
+    @Test
+    fun registerContentObserver_inputUri_notifyForDescendants_true() {
+        mSettings.registerContentObserver(
+            TEST_SETTING_URI,
+            notifyForDescendants = true,
+            mContentObserver
+        )
+        verify(mSettings.getContentResolver())
+            .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver), eq(0))
+    }
+
+    @Test
+    fun getString_keyPresent_returnValidValue() {
+        mSettings.putString(TEST_SETTING, "test")
+        assertThat(mSettings.getString(TEST_SETTING)).isEqualTo("test")
+    }
+
+    @Test
+    fun getString_keyAbsent_returnEmptyValue() {
+        assertThat(mSettings.getString(TEST_SETTING)).isEmpty()
+    }
+
+    @Test
+    fun getStringForUser_multipleUsers_validResult() {
+        mSettings.putStringForUser(TEST_SETTING, "test", MAIN_USER_ID)
+        mSettings.putStringForUser(TEST_SETTING, "test1", SECONDARY_USER_ID)
+        assertThat(mSettings.getStringForUser(TEST_SETTING, MAIN_USER_ID)).isEqualTo("test")
+        assertThat(mSettings.getStringForUser(TEST_SETTING, SECONDARY_USER_ID)).isEqualTo("test1")
+    }
+
+    @Test
+    fun getInt_keyPresent_returnValidValue() {
+        mSettings.putInt(TEST_SETTING, 2)
+        assertThat(mSettings.getInt(TEST_SETTING)).isEqualTo(2)
+    }
+
+    @Test
+    fun getInt_keyPresent_nonIntegerValue_throwException() {
+        assertThrows(Settings.SettingNotFoundException::class.java) {
+            mSettings.putString(TEST_SETTING, "test")
+            mSettings.getInt(TEST_SETTING)
+        }
+    }
+
+    @Test
+    fun getInt_keyAbsent_throwException() {
+        assertThrows(Settings.SettingNotFoundException::class.java) {
+            mSettings.getInt(TEST_SETTING)
+        }
+    }
+
+    @Test
+    fun getInt_keyAbsent_returnDefaultValue() {
+        assertThat(mSettings.getInt(TEST_SETTING, 5)).isEqualTo(5)
+    }
+
+    @Test
+    fun getIntForUser_multipleUsers__validResult() {
+        mSettings.putIntForUser(TEST_SETTING, 1, MAIN_USER_ID)
+        mSettings.putIntForUser(TEST_SETTING, 2, SECONDARY_USER_ID)
+        assertThat(mSettings.getIntForUser(TEST_SETTING, MAIN_USER_ID)).isEqualTo(1)
+        assertThat(mSettings.getIntForUser(TEST_SETTING, SECONDARY_USER_ID)).isEqualTo(2)
+    }
+
+    @Test
+    fun getBool_keyPresent_returnValidValue() {
+        mSettings.putBool(TEST_SETTING, true)
+        assertThat(mSettings.getBool(TEST_SETTING)).isTrue()
+    }
+
+    @Test
+    fun getBool_keyPresent_nonBooleanValue_throwException() {
+        assertThrows(Settings.SettingNotFoundException::class.java) {
+            mSettings.putString(TEST_SETTING, "test")
+            mSettings.getBool(TEST_SETTING)
+        }
+    }
+
+    @Test
+    fun getBool_keyAbsent_throwException() {
+        assertThrows(Settings.SettingNotFoundException::class.java) {
+            mSettings.getBool(TEST_SETTING)
+        }
+    }
+
+    @Test
+    fun getBool_keyAbsent_returnDefaultValue() {
+        assertThat(mSettings.getBool(TEST_SETTING, false)).isEqualTo(false)
+    }
+
+    @Test
+    fun getBoolForUser_multipleUsers__validResult() {
+        mSettings.putBoolForUser(TEST_SETTING, true, MAIN_USER_ID)
+        mSettings.putBoolForUser(TEST_SETTING, false, SECONDARY_USER_ID)
+        assertThat(mSettings.getBoolForUser(TEST_SETTING, MAIN_USER_ID)).isEqualTo(true)
+        assertThat(mSettings.getBoolForUser(TEST_SETTING, SECONDARY_USER_ID)).isEqualTo(false)
+    }
+
+    @Test
+    fun getLong_keyPresent_returnValidValue() {
+        mSettings.putLong(TEST_SETTING, 1L)
+        assertThat(mSettings.getLong(TEST_SETTING)).isEqualTo(1L)
+    }
+
+    @Test
+    fun getLong_keyPresent_nonLongValue_throwException() {
+        assertThrows(Settings.SettingNotFoundException::class.java) {
+            mSettings.putString(TEST_SETTING, "test")
+            mSettings.getLong(TEST_SETTING)
+        }
+    }
+
+    @Test
+    fun getLong_keyAbsent_throwException() {
+        assertThrows(Settings.SettingNotFoundException::class.java) {
+            mSettings.getLong(TEST_SETTING)
+        }
+    }
+
+    @Test
+    fun getLong_keyAbsent_returnDefaultValue() {
+        assertThat(mSettings.getLong(TEST_SETTING, 2L)).isEqualTo(2L)
+    }
+
+    @Test
+    fun getLongForUser_multipleUsers__validResult() {
+        mSettings.putLongForUser(TEST_SETTING, 1L, MAIN_USER_ID)
+        mSettings.putLongForUser(TEST_SETTING, 2L, SECONDARY_USER_ID)
+        assertThat(mSettings.getLongForUser(TEST_SETTING, MAIN_USER_ID)).isEqualTo(1L)
+        assertThat(mSettings.getLongForUser(TEST_SETTING, SECONDARY_USER_ID)).isEqualTo(2L)
+    }
+
+    @Test
+    fun getFloat_keyPresent_returnValidValue() {
+        mSettings.putFloat(TEST_SETTING, 2.5F)
+        assertThat(mSettings.getFloat(TEST_SETTING)).isEqualTo(2.5F)
+    }
+
+    @Test
+    fun getFloat_keyPresent_nonFloatValue_throwException() {
+        assertThrows(Settings.SettingNotFoundException::class.java) {
+            mSettings.putString(TEST_SETTING, "test")
+            mSettings.getFloat(TEST_SETTING)
+        }
+    }
+
+    @Test
+    fun getFloat_keyAbsent_throwException() {
+        assertThrows(Settings.SettingNotFoundException::class.java) {
+            mSettings.getFloat(TEST_SETTING)
+        }
+    }
+
+    @Test
+    fun getFloat_keyAbsent_returnDefaultValue() {
+        assertThat(mSettings.getFloat(TEST_SETTING, 2.5F)).isEqualTo(2.5F)
+    }
+
+    @Test
+    fun getFloatForUser_multipleUsers__validResult() {
+        mSettings.putFloatForUser(TEST_SETTING, 1F, MAIN_USER_ID)
+        mSettings.putFloatForUser(TEST_SETTING, 2F, SECONDARY_USER_ID)
+        assertThat(mSettings.getFloatForUser(TEST_SETTING, MAIN_USER_ID)).isEqualTo(1F)
+        assertThat(mSettings.getFloatForUser(TEST_SETTING, SECONDARY_USER_ID)).isEqualTo(2F)
+    }
+
+    /**
+     * Fake implementation of [UserSettingsProxy].
+     *
+     * This class uses a mock of [ContentResolver] to test the [ContentObserver] registration APIs.
+     */
+    private class FakeUserSettingsProxy(override val userTracker: UserTracker) : UserSettingsProxy {
+
+        private val mContentResolver = mock(ContentResolver::class.java)
+        private val userIdToSettingsValueMap: MutableMap<Int, MutableMap<String, String>> =
+            mutableMapOf()
+
+        override fun getContentResolver() = mContentResolver
+
+        override fun getUriFor(name: String) =
+            Uri.parse(StringBuilder().append(URI_PREFIX).append(name).toString())
+
+        override fun getStringForUser(name: String, userHandle: Int) =
+            userIdToSettingsValueMap[userHandle]?.get(name) ?: ""
+
+        override fun putString(
+            name: String,
+            value: String,
+            overrideableByRestore: Boolean
+        ): Boolean {
+            userIdToSettingsValueMap[DEFAULT_USER_ID]?.put(name, value)
+            return true
+        }
+
+        override fun putString(
+            name: String,
+            value: String,
+            tag: String,
+            makeDefault: Boolean
+        ): Boolean {
+            putStringForUser(name, value, DEFAULT_USER_ID)
+            return true
+        }
+
+        override fun putStringForUser(name: String, value: String, userHandle: Int): Boolean {
+            userIdToSettingsValueMap[userHandle] = mutableMapOf(Pair(name, value))
+            return true
+        }
+
+        override fun putStringForUser(
+            name: String,
+            value: String,
+            tag: String?,
+            makeDefault: Boolean,
+            userHandle: Int,
+            overrideableByRestore: Boolean
+        ): Boolean {
+            userIdToSettingsValueMap[userHandle]?.set(name, value)
+            return true
+        }
+
+        private companion object {
+            const val DEFAULT_USER_ID = 0
+            const val URI_PREFIX = "content://settings/"
+        }
+    }
+
+    private companion object {
+        const val MAIN_USER_ID = 10
+        const val SECONDARY_USER_ID = 20
+        const val TEST_SETTING = "test_setting"
+        val TEST_SETTING_URI = Uri.parse("content://settings/test_setting")
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index a81ac03..0e95320 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -55,6 +55,7 @@
 import com.android.systemui.shade.data.repository.shadeRepository
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.shade.shadeController
+import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
 import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
@@ -78,6 +79,7 @@
     val configurationInteractor by lazy { kosmos.configurationInteractor }
     val bouncerRepository by lazy { kosmos.bouncerRepository }
     val communalRepository by lazy { kosmos.fakeCommunalRepository }
+    val headsUpNotificationInteractor by lazy { kosmos.headsUpNotificationInteractor }
     val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
     val keyguardBouncerRepository by lazy { kosmos.fakeKeyguardBouncerRepository }
     val keyguardInteractor by lazy { kosmos.keyguardInteractor }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeHeadsUpRowRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeHeadsUpRowRepository.kt
index 2e983a8..980d65f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeHeadsUpRowRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeHeadsUpRowRepository.kt
@@ -18,7 +18,11 @@
 
 import kotlinx.coroutines.flow.MutableStateFlow
 
-class FakeHeadsUpRowRepository(override val key: String, override val elementKey: Any) :
+class FakeHeadsUpRowRepository(override val key: String, override val elementKey: Any = Any()) :
     HeadsUpRowRepository {
+    constructor(key: String, isPinned: Boolean) : this(key = key) {
+        this.isPinned.value = isPinned
+    }
+
     override val isPinned: MutableStateFlow<Boolean> = MutableStateFlow(false)
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HeadsUpNotificationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HeadsUpNotificationInteractorKosmos.kt
index d345107..c74aec1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HeadsUpNotificationInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HeadsUpNotificationInteractorKosmos.kt
@@ -16,11 +16,20 @@
 
 package com.android.systemui.statusbar.notification.stack.domain.interactor
 
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
 import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
 
 val Kosmos.headsUpNotificationInteractor by Fixture {
-    HeadsUpNotificationInteractor(headsUpNotificationRepository)
+    HeadsUpNotificationInteractor(
+        headsUpNotificationRepository,
+        deviceEntryFaceAuthInteractor,
+        keyguardTransitionInteractor,
+        notificationsKeyguardInteractor,
+        shadeInteractor,
+    )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
index 94f6ecd..de8b350 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
 import com.android.systemui.dump.dumpManager
-import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.kosmos.testDispatcher
@@ -42,7 +41,6 @@
         activeNotificationsInteractor,
         notificationStackInteractor,
         headsUpNotificationInteractor,
-        keyguardInteractor,
         remoteInputInteractor,
         seenNotificationsInteractor,
         shadeInteractor,
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 58855ea..4c8f416 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5590,32 +5590,30 @@
             // security checking for it above.
             userId = UserHandle.USER_CURRENT;
         }
-        try {
-            if (owningUid != 0 && owningUid != SYSTEM_UID) {
-                final int uid = AppGlobals.getPackageManager().getPackageUid(packageName,
-                        MATCH_DEBUG_TRIAGED_MISSING, UserHandle.getUserId(owningUid));
-                if (!UserHandle.isSameApp(owningUid, uid)) {
-                    String msg = "Permission Denial: getIntentSender() from pid="
-                            + Binder.getCallingPid()
-                            + ", uid=" + owningUid
-                            + ", (need uid=" + uid + ")"
-                            + " is not allowed to send as package " + packageName;
-                    Slog.w(TAG, msg);
-                    throw new SecurityException(msg);
-                }
-            }
 
-            if (type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT) {
-                return mAtmInternal.getIntentSender(type, packageName, featureId, owningUid,
-                        userId, token, resultWho, requestCode, intents, resolvedTypes, flags,
-                        bOptions);
+        if (owningUid != 0 && owningUid != SYSTEM_UID) {
+            if (!getPackageManagerInternal().isSameApp(
+                    packageName,
+                    MATCH_DEBUG_TRIAGED_MISSING,
+                    owningUid,
+                    UserHandle.getUserId(owningUid))) {
+                String msg = "Permission Denial: getIntentSender() from pid="
+                        + Binder.getCallingPid()
+                        + ", uid=" + owningUid
+                        + " is not allowed to send as package " + packageName;
+                Slog.w(TAG, msg);
+                throw new SecurityException(msg);
             }
-            return mPendingIntentController.getIntentSender(type, packageName, featureId,
-                    owningUid, userId, token, resultWho, requestCode, intents, resolvedTypes,
-                    flags, bOptions);
-        } catch (RemoteException e) {
-            throw new SecurityException(e);
         }
+
+        if (type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT) {
+            return mAtmInternal.getIntentSender(type, packageName, featureId, owningUid,
+                    userId, token, resultWho, requestCode, intents, resolvedTypes, flags,
+                    bOptions);
+        }
+        return mPendingIntentController.getIntentSender(type, packageName, featureId,
+                owningUid, userId, token, resultWho, requestCode, intents, resolvedTypes,
+                flags, bOptions);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
index 9600317..a67af89 100644
--- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -84,6 +84,7 @@
 import java.util.Arrays;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
+import java.util.function.ToIntFunction;
 
 /**
  * A modern implementation of the oom adjuster.
@@ -271,11 +272,31 @@
         // The last node besides the tail.
         private final ProcessRecordNode[] mLastNode;
 
+        private final ToIntFunction<ProcessRecord> mSlotFunction;
+        // Cache of the most important slot with a node in it.
+        private int mFirstPopulatedSlot = 0;
+
         ProcessRecordNodes(@ProcessRecordNode.NodeType int type, int size) {
             mType = type;
+            final ToIntFunction<ProcessRecord> valueFunction;
+            switch (mType) {
+                case ProcessRecordNode.NODE_TYPE_PROC_STATE:
+                    valueFunction = (proc) -> proc.mState.getCurProcState();
+                    mSlotFunction = (proc) -> processStateToSlot(proc.mState.getCurProcState());
+                    break;
+                case ProcessRecordNode.NODE_TYPE_ADJ:
+                    valueFunction = (proc) -> proc.mState.getCurRawAdj();
+                    mSlotFunction = (proc) -> adjToSlot(proc.mState.getCurRawAdj());
+                    break;
+                default:
+                    valueFunction = (proc) -> 0;
+                    mSlotFunction = (proc) -> 0;
+                    break;
+            }
+
             mProcessRecordNodes = new LinkedProcessRecordList[size];
             for (int i = 0; i < size; i++) {
-                mProcessRecordNodes[i] = new LinkedProcessRecordList(type);
+                mProcessRecordNodes[i] = new LinkedProcessRecordList(valueFunction);
             }
             mLastNode = new ProcessRecordNode[size];
             resetLastNodes();
@@ -294,6 +315,11 @@
         }
 
         void resetLastNodes() {
+            if (Flags.simplifyProcessTraversal()) {
+                // Last nodes are no longer used. Just reset instead.
+                reset();
+                return;
+            }
             for (int i = 0; i < mProcessRecordNodes.length; i++) {
                 mLastNode[i] = mProcessRecordNodes[i].getLastNodeBeforeTail();
             }
@@ -308,6 +334,36 @@
             final ProcessRecordNode tail = mProcessRecordNodes[slot].TAIL;
             while (node != tail) {
                 mTmpOomAdjusterArgs.mApp = node.mApp;
+                if (node.mApp == null) {
+                    // TODO(b/336178916) - Temporary logging for root causing b/336178916.
+                    StringBuilder sb = new StringBuilder();
+                    sb.append("Iterating null process during OomAdjuster traversal!!!\n");
+                    sb.append("Type:");
+                    switch (mType) {
+                        case ProcessRecordNode.NODE_TYPE_PROC_STATE -> sb.append(
+                                "NODE_TYPE_PROC_STATE");
+                        case ProcessRecordNode.NODE_TYPE_ADJ -> sb.append("NODE_TYPE_ADJ");
+                        default -> sb.append("UNKNOWN");
+                    }
+                    sb.append(", Slot:");
+                    sb.append(slot);
+                    sb.append("\nLAST:");
+                    ProcessRecordNode last = mLastNode[slot];
+                    if (last.mApp == null) {
+                        sb.append("null");
+                    } else {
+                        sb.append(last);
+                        sb.append("\nSetProcState:");
+                        sb.append(last.mApp.getSetProcState());
+                        sb.append(", CurProcState:");
+                        sb.append(last.mApp.mState.getCurProcState());
+                        sb.append(", SetAdj:");
+                        sb.append(last.mApp.getSetAdj());
+                        sb.append(", CurRawAdj:");
+                        sb.append(last.mApp.mState.getCurRawAdj());
+                    }
+                    Slog.wtfStack(TAG, sb.toString());
+                }
                 // Save the next before calling callback, since that may change the node.mNext.
                 final ProcessRecordNode next = node.mNext;
                 callback.accept(mTmpOomAdjusterArgs);
@@ -325,6 +381,33 @@
             }
         }
 
+        ProcessRecord poll() {
+            ProcessRecordNode node = null;
+            final int size = mProcessRecordNodes.length;
+            // Find the next node.
+            while (node == null && mFirstPopulatedSlot < size) {
+                node = mProcessRecordNodes[mFirstPopulatedSlot].poll();
+                if (node == null) {
+                    // This slot is now empty, move on to the next.
+                    mFirstPopulatedSlot++;
+                }
+            }
+            if (node == null) return null;
+            return node.mApp;
+        }
+
+        void offer(ProcessRecord proc) {
+            ProcessRecordNode node = proc.mLinkedNodes[mType];
+            // Find which slot to add the node to.
+            final int newSlot = mSlotFunction.applyAsInt(proc);
+            if (newSlot < mFirstPopulatedSlot) {
+                // node is being added to a more important slot.
+                mFirstPopulatedSlot = newSlot;
+            }
+            node.unlink();
+            mProcessRecordNodes[newSlot].offer(node);
+        }
+
         int getNumberOfSlots() {
             return mProcessRecordNodes.length;
         }
@@ -423,12 +506,35 @@
             // Sentinel head/tail, to make bookkeeping work easier.
             final ProcessRecordNode HEAD = new ProcessRecordNode(null);
             final ProcessRecordNode TAIL = new ProcessRecordNode(null);
-            final @ProcessRecordNode.NodeType int mNodeType;
+            final ToIntFunction<ProcessRecord> mValueFunction;
 
-            LinkedProcessRecordList(@ProcessRecordNode.NodeType int nodeType) {
+            LinkedProcessRecordList(ToIntFunction<ProcessRecord> valueFunction) {
                 HEAD.mNext = TAIL;
                 TAIL.mPrev = HEAD;
-                mNodeType = nodeType;
+                mValueFunction = valueFunction;
+            }
+
+            ProcessRecordNode poll() {
+                final ProcessRecordNode next = HEAD.mNext;
+                if (next == TAIL) return null;
+                next.unlink();
+                return next;
+            }
+
+            void offer(@NonNull ProcessRecordNode node) {
+                final int newValue = mValueFunction.applyAsInt(node.mApp);
+
+                // Find the last node with less than or equal value to the new node.
+                ProcessRecordNode curNode = TAIL.mPrev;
+                while (curNode != HEAD && mValueFunction.applyAsInt(curNode.mApp) > newValue) {
+                    curNode = curNode.mPrev;
+                }
+
+                // Insert the new node after the found node.
+                node.mPrev = curNode;
+                node.mNext = curNode.mNext;
+                curNode.mNext.mPrev = node;
+                curNode.mNext = node;
             }
 
             void append(@NonNull ProcessRecordNode node) {
@@ -727,34 +833,50 @@
 
     private void updateAdjSlotIfNecessary(ProcessRecord app, int prevRawAdj) {
         if (app.mState.getCurRawAdj() != prevRawAdj) {
-            final int slot = adjToSlot(app.mState.getCurRawAdj());
-            final int prevSlot = adjToSlot(prevRawAdj);
-            if (slot != prevSlot && slot != ADJ_SLOT_INVALID) {
-                mProcessRecordAdjNodes.moveAppTo(app, prevSlot, slot);
+            if (Flags.simplifyProcessTraversal()) {
+                mProcessRecordAdjNodes.offer(app);
+            } else {
+                final int slot = adjToSlot(app.mState.getCurRawAdj());
+                final int prevSlot = adjToSlot(prevRawAdj);
+                if (slot != prevSlot && slot != ADJ_SLOT_INVALID) {
+                    mProcessRecordAdjNodes.moveAppTo(app, prevSlot, slot);
+                }
             }
         }
     }
 
     private void updateAdjSlot(ProcessRecord app, int prevRawAdj) {
-        final int slot = adjToSlot(app.mState.getCurRawAdj());
-        final int prevSlot = adjToSlot(prevRawAdj);
-        mProcessRecordAdjNodes.moveAppTo(app, prevSlot, slot);
+        if (Flags.simplifyProcessTraversal()) {
+            mProcessRecordAdjNodes.offer(app);
+        } else {
+            final int slot = adjToSlot(app.mState.getCurRawAdj());
+            final int prevSlot = adjToSlot(prevRawAdj);
+            mProcessRecordAdjNodes.moveAppTo(app, prevSlot, slot);
+        }
     }
 
     private void updateProcStateSlotIfNecessary(ProcessRecord app, int prevProcState) {
         if (app.mState.getCurProcState() != prevProcState) {
-            final int slot = processStateToSlot(app.mState.getCurProcState());
-            final int prevSlot = processStateToSlot(prevProcState);
-            if (slot != prevSlot) {
-                mProcessRecordProcStateNodes.moveAppTo(app, prevSlot, slot);
+            if (Flags.simplifyProcessTraversal()) {
+                mProcessRecordProcStateNodes.offer(app);
+            } else {
+                final int slot = processStateToSlot(app.mState.getCurProcState());
+                final int prevSlot = processStateToSlot(prevProcState);
+                if (slot != prevSlot) {
+                    mProcessRecordProcStateNodes.moveAppTo(app, prevSlot, slot);
+                }
             }
         }
     }
 
     private void updateProcStateSlot(ProcessRecord app, int prevProcState) {
-        final int slot = processStateToSlot(app.mState.getCurProcState());
-        final int prevSlot = processStateToSlot(prevProcState);
-        mProcessRecordProcStateNodes.moveAppTo(app, prevSlot, slot);
+        if (Flags.simplifyProcessTraversal()) {
+            mProcessRecordProcStateNodes.offer(app);
+        } else {
+            final int slot = processStateToSlot(app.mState.getCurProcState());
+            final int prevSlot = processStateToSlot(prevProcState);
+            mProcessRecordProcStateNodes.moveAppTo(app, prevSlot, slot);
+        }
     }
 
     @Override
@@ -832,8 +954,15 @@
             // Compute initial values, the procState and adj priority queues will be populated here.
             computeOomAdjLSP(app, UNKNOWN_ADJ, topApp, true, now, false, false, oomAdjReason,
                     false);
-            updateProcStateSlot(app, prevProcState);
-            updateAdjSlot(app, prevAdj);
+
+            if (Flags.simplifyProcessTraversal()) {
+                // Just add to the procState priority queue. The adj priority queue should be
+                // empty going into the traversal step.
+                mProcessRecordProcStateNodes.offer(app);
+            } else {
+                updateProcStateSlot(app, prevProcState);
+                updateAdjSlot(app, prevAdj);
+            }
         }
 
         // Set adj last nodes now, this way a process will only be reevaluated during the adj node
@@ -851,14 +980,32 @@
      */
     @GuardedBy({"mService", "mProcLock"})
     private void computeConnectionsLSP() {
-        // 1st pass, scan each slot in the procstate node list.
-        for (int i = 0, end = mProcessRecordProcStateNodes.size() - 1; i < end; i++) {
-            mProcessRecordProcStateNodes.forEachNewNode(i, mComputeConnectionsConsumer);
-        }
+        if (Flags.simplifyProcessTraversal()) {
+            // 1st pass, iterate all nodes in order of procState importance.
+            ProcessRecord proc = mProcessRecordProcStateNodes.poll();
+            while (proc != null) {
+                mTmpOomAdjusterArgs.mApp = proc;
+                mComputeConnectionsConsumer.accept(mTmpOomAdjusterArgs);
+                proc = mProcessRecordProcStateNodes.poll();
+            }
 
-        // 2nd pass, scan each slot in the adj node list.
-        for (int i = 0, end = mProcessRecordAdjNodes.size() - 1; i < end; i++) {
-            mProcessRecordAdjNodes.forEachNewNode(i, mComputeConnectionsConsumer);
+            // 2st pass, iterate all nodes in order of procState importance.
+            proc = mProcessRecordAdjNodes.poll();
+            while (proc != null) {
+                mTmpOomAdjusterArgs.mApp = proc;
+                mComputeConnectionsConsumer.accept(mTmpOomAdjusterArgs);
+                proc = mProcessRecordAdjNodes.poll();
+            }
+        } else {
+            // 1st pass, scan each slot in the procstate node list.
+            for (int i = 0, end = mProcessRecordProcStateNodes.size() - 1; i < end; i++) {
+                mProcessRecordProcStateNodes.forEachNewNode(i, mComputeConnectionsConsumer);
+            }
+
+            // 2nd pass, scan each slot in the adj node list.
+            for (int i = 0, end = mProcessRecordAdjNodes.size() - 1; i < end; i++) {
+                mProcessRecordAdjNodes.forEachNewNode(i, mComputeConnectionsConsumer);
+            }
         }
     }
 
@@ -987,8 +1134,14 @@
             args.mApp = reachable;
             computeOomAdjIgnoringReachablesLSP(args);
 
-            updateProcStateSlot(reachable, prevProcState);
-            updateAdjSlot(reachable, prevAdj);
+            if (Flags.simplifyProcessTraversal()) {
+                // Just add to the procState priority queue. The adj priority queue should be
+                // empty going into the traversal step.
+                mProcessRecordProcStateNodes.offer(reachable);
+            } else {
+                updateProcStateSlot(reachable, prevProcState);
+                updateAdjSlot(reachable, prevAdj);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index da45a77..8d7a1c9 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -18,6 +18,10 @@
 
 import static android.app.ActivityManager.PROCESS_STATE_TOP;
 import static android.app.ActivityManager.START_SUCCESS;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_COMPAT;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
 
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -389,13 +393,20 @@
 
     private static BackgroundStartPrivileges getBackgroundStartPrivilegesAllowedByCaller(
             @Nullable Bundle options, int callingUid, @Nullable String callingPackage) {
-        if (options == null || !options.containsKey(
-                        ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED)) {
+        if (options == null) {
             return getDefaultBackgroundStartPrivileges(callingUid, callingPackage);
         }
-        return options.getBoolean(ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED)
-                ? BackgroundStartPrivileges.ALLOW_BAL
-                : BackgroundStartPrivileges.NONE;
+        switch (options.getInt(ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED,
+                MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED)) {
+            case MODE_BACKGROUND_ACTIVITY_START_DENIED:
+                return BackgroundStartPrivileges.NONE;
+            case MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED:
+                return getDefaultBackgroundStartPrivileges(callingUid, callingPackage);
+            case MODE_BACKGROUND_ACTIVITY_START_ALLOWED:
+            case MODE_BACKGROUND_ACTIVITY_START_COMPAT:
+            default:
+                return BackgroundStartPrivileges.ALLOW_BAL;
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 9bf5c21..032093b 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -342,33 +342,29 @@
             AsyncTask.THREAD_POOL_EXECUTOR,
             (DeviceConfig.Properties properties) -> {
 
-              HashMap<String, HashMap<String, String>> propsToStage =
-                  getStagedFlagsWithValueChange(properties);
-
-              // send prop stage request to sys prop
-              for (HashMap.Entry<String, HashMap<String, String>> entry : propsToStage.entrySet()) {
-                String actualNamespace = entry.getKey();
-                HashMap<String, String> flagValuesToStage = entry.getValue();
-
-                for (String flagName : flagValuesToStage.keySet()) {
-                  String stagedValue = flagValuesToStage.get(flagName);
-                  String propertyName = "next_boot." + makeAconfigFlagPropertyName(
-                      actualNamespace, flagName);
-
-                  if (!propertyName.matches(SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX)
-                      || propertyName.contains(SYSTEM_PROPERTY_INVALID_SUBSTRING)) {
-                    logErr("unable to construct system property for " + actualNamespace
-                        + "/" + flagName);
-                    continue;
+              for (String flagName : properties.getKeyset()) {
+                  String flagValue = properties.getString(flagName, null);
+                  if (flagName == null || flagValue == null) {
+                      continue;
                   }
 
-                  setProperty(propertyName, stagedValue);
-                }
+                  int idx = flagName.indexOf(NAMESPACE_REBOOT_STAGING_DELIMITER);
+                  if (idx == -1 || idx == flagName.length() - 1 || idx == 0) {
+                      logErr("invalid staged flag: " + flagName);
+                      continue;
+                  }
+
+                  String actualNamespace = flagName.substring(0, idx);
+                  String actualFlagName = flagName.substring(idx+1);
+                  String propertyName = "next_boot." + makeAconfigFlagPropertyName(
+                      actualNamespace, actualFlagName);
+
+                  setProperty(propertyName, flagValue);
               }
 
               // send prop stage request to new storage
               if (enableAconfigStorageDaemon()) {
-                  stageFlagsInNewStorage(propsToStage);
+                  stageFlagsInNewStorage(properties);
               }
 
         });
@@ -607,25 +603,33 @@
      * @param propsToStage
      */
     @VisibleForTesting
-    static void stageFlagsInNewStorage(HashMap<String, HashMap<String, String>> propsToStage) {
+    static void stageFlagsInNewStorage(DeviceConfig.Properties props) {
         // write aconfigd requests proto to proto output stream
         int num_requests = 0;
         ProtoOutputStream requests = new ProtoOutputStream();
-        for (HashMap.Entry<String, HashMap<String, String>> entry : propsToStage.entrySet()) {
-            String actualNamespace = entry.getKey();
-            HashMap<String, String> flagValuesToStage = entry.getValue();
-            for (String fullFlagName : flagValuesToStage.keySet()) {
-                String stagedValue = flagValuesToStage.get(fullFlagName);
-                int idx = fullFlagName.lastIndexOf(".");
-                if (idx == -1) {
-                    logErr("invalid flag name: " + fullFlagName);
-                    continue;
-                }
-                String packageName = fullFlagName.substring(0, idx);
-                String flagName = fullFlagName.substring(idx+1);
-                writeFlagOverrideRequest(requests, packageName, flagName, stagedValue, false);
-                ++num_requests;
+        for (String flagName : props.getKeyset()) {
+            String flagValue = props.getString(flagName, null);
+            if (flagName == null || flagValue == null) {
+                continue;
             }
+
+            int idx = flagName.indexOf("*");
+            if (idx == -1 || idx == flagName.length() - 1 || idx == 0) {
+                logErr("invalid local flag override: " + flagName);
+                continue;
+            }
+            String actualNamespace = flagName.substring(0, idx);
+            String fullFlagName = flagName.substring(idx+1);
+
+            idx = fullFlagName.lastIndexOf(".");
+            if (idx == -1) {
+                logErr("invalid flag name: " + fullFlagName);
+                continue;
+            }
+            String packageName = fullFlagName.substring(0, idx);
+            String realFlagName = fullFlagName.substring(idx+1);
+            writeFlagOverrideRequest(requests, packageName, realFlagName, flagValue, false);
+            ++num_requests;
         }
 
         if (num_requests == 0) {
@@ -637,7 +641,7 @@
 
         // deserialize back using proto input stream
         try {
-          parseAndLogAconfigdReturn(returns);
+            parseAndLogAconfigdReturn(returns);
         } catch (IOException ioe) {
             logErr("failed to parse aconfigd return", ioe);
         }
@@ -665,63 +669,6 @@
         return propertyName;
     }
 
-    /**
-     * Get the flags that need to be staged in sys prop, only these with a real value
-     * change needs to be staged in sys prop. Otherwise, the flag stage is useless and
-     * create performance problem at sys prop side.
-     * @param properties
-     * @return a hash map of namespace name to actual flags to stage
-     */
-    @VisibleForTesting
-    static HashMap<String, HashMap<String, String>> getStagedFlagsWithValueChange(
-        DeviceConfig.Properties properties) {
-
-      // sort flags by actual namespace of the flag
-      HashMap<String, HashMap<String, String>> stagedProps = new HashMap<>();
-      for (String flagName : properties.getKeyset()) {
-        int idx = flagName.indexOf(NAMESPACE_REBOOT_STAGING_DELIMITER);
-        if (idx == -1 || idx == flagName.length() - 1 || idx == 0) {
-          logErr("invalid staged flag: " + flagName);
-          continue;
-        }
-        String actualNamespace = flagName.substring(0, idx);
-        String actualFlagName = flagName.substring(idx+1);
-        HashMap<String, String> flagStagedValues = stagedProps.get(actualNamespace);
-        if (flagStagedValues == null) {
-          flagStagedValues = new HashMap<String, String>();
-          stagedProps.put(actualNamespace, flagStagedValues);
-        }
-        flagStagedValues.put(actualFlagName, properties.getString(flagName, null));
-      }
-
-      // for each namespace, find flags with real flag value change
-      HashMap<String, HashMap<String, String>> propsToStage = new HashMap<>();
-      for (HashMap.Entry<String, HashMap<String, String>> entry : stagedProps.entrySet()) {
-        String actualNamespace = entry.getKey();
-        HashMap<String, String> flagStagedValues = entry.getValue();
-        Map<String, String> flagCurrentValues = Settings.Config.getStrings(
-            actualNamespace, new ArrayList<String>(flagStagedValues.keySet()));
-
-        HashMap<String, String> flagsToStage = new HashMap<>();
-        for (String flagName : flagStagedValues.keySet()) {
-          String stagedValue = flagStagedValues.get(flagName);
-          String currentValue = flagCurrentValues.get(flagName);
-          if (stagedValue == null) {
-            continue;
-          }
-          if (currentValue == null || !stagedValue.equalsIgnoreCase(currentValue)) {
-            flagsToStage.put(flagName, stagedValue);
-          }
-        }
-
-        if (!flagsToStage.isEmpty()) {
-          propsToStage.put(actualNamespace, flagsToStage);
-        }
-      }
-
-      return propsToStage;
-    }
-
     private void setProperty(String key, String value) {
         // Check if need to clear the property
         if (value == null) {
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index b7108df..afde4f7 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -123,3 +123,14 @@
     description: "Avoid OomAdjuster calculations for connections that won't change importance"
     bug: "323376416"
 }
+
+flag {
+    name: "simplify_process_traversal"
+    namespace: "backstage_power"
+    description: "Simplify the OomAdjuster's process traversal mechanism."
+    bug: "336178916"
+    is_fixed_read_only: true
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 475334c..1dc1846 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1439,7 +1439,6 @@
         sendMsgNoDelay(MSG_BROADCAST_AUDIO_BECOMING_NOISY, SENDMSG_REPLACE);
     }
 
-    @GuardedBy("mDeviceStateLock")
     /*package*/ void postBluetoothActiveDevice(BtDeviceInfo info, int delay) {
         sendLMsg(MSG_L_SET_BT_ACTIVE_DEVICE, SENDMSG_QUEUE, info, delay);
     }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 15c5c10..7deef2f 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -7897,7 +7897,7 @@
                     + previousDevice + " -> " + newDevice + ". Got: " + profile);
         }
 
-        sDeviceLogger.enqueue(new EventLogger.StringEvent("BlutoothActiveDeviceChanged for "
+        sDeviceLogger.enqueue(new EventLogger.StringEvent("BluetoothActiveDeviceChanged for "
                 + BluetoothProfile.getProfileName(profile) + ", device update " + previousDevice
                 + " -> " + newDevice).printLog(TAG));
         AudioDeviceBroker.BtDeviceChangedData data =
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index a649d34..07daecd 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -289,6 +289,7 @@
                     Log.e(TAG, "Exception while getting status of " + device, e);
                 }
                 if (btCodecStatus == null) {
+                    Log.e(TAG, "getCodec, null A2DP codec status for device: " + device);
                     mA2dpCodecConfig = null;
                     return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, changed);
                 }
@@ -316,6 +317,7 @@
                     Log.e(TAG, "Exception while getting status of " + device, e);
                 }
                 if (btLeCodecStatus == null) {
+                    Log.e(TAG, "getCodec, null LE codec status for device: " + device);
                     mLeAudioCodecConfig = null;
                     return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, changed);
                 }
@@ -363,6 +365,7 @@
             return new Pair<>(profile == BluetoothProfile.A2DP
                     ? AudioSystem.AUDIO_FORMAT_SBC : AudioSystem.AUDIO_FORMAT_LC3, true);
         }
+
         return codecAndChanged;
     }
 
@@ -653,7 +656,7 @@
                 // Not a valid profile to connect
                 Log.e(TAG, "onBtProfileConnected: Not a valid profile to connect "
                         + BluetoothProfile.getProfileName(profile));
-                break;
+                return;
         }
 
         // this part is only for A2DP, LE Audio unicast and Hearing aid
@@ -664,17 +667,65 @@
             return;
         }
         List<BluetoothDevice> activeDevices = adapter.getActiveDevices(profile);
-        BluetoothProfileConnectionInfo bpci = new BluetoothProfileConnectionInfo(profile);
-        for (BluetoothDevice device : activeDevices) {
-            if (device == null) {
-                continue;
-            }
-            AudioDeviceBroker.BtDeviceChangedData data = new AudioDeviceBroker.BtDeviceChangedData(
-                    device, null, bpci, "mBluetoothProfileServiceListener");
-            AudioDeviceBroker.BtDeviceInfo info = mDeviceBroker.createBtDeviceInfo(
-                    data, device, BluetoothProfile.STATE_CONNECTED);
-            mDeviceBroker.postBluetoothActiveDevice(info, 0 /* delay */);
+        if (activeDevices.isEmpty() || activeDevices.get(0) == null) {
+            return;
         }
+        BluetoothDevice device = activeDevices.get(0);
+        switch (profile) {
+            case BluetoothProfile.A2DP: {
+                BluetoothProfileConnectionInfo bpci =
+                        BluetoothProfileConnectionInfo.createA2dpInfo(false, -1);
+                postBluetoothActiveDevice(device, bpci);
+            } break;
+            case BluetoothProfile.HEARING_AID: {
+                BluetoothProfileConnectionInfo bpci =
+                        BluetoothProfileConnectionInfo.createHearingAidInfo(false);
+                postBluetoothActiveDevice(device, bpci);
+            } break;
+            case BluetoothProfile.LE_AUDIO: {
+                int groupId = mLeAudio.getGroupId(device);
+                BluetoothLeAudioCodecStatus btLeCodecStatus = null;
+                try {
+                    btLeCodecStatus = mLeAudio.getCodecStatus(groupId);
+                } catch (Exception e) {
+                    Log.e(TAG, "Exception while getting status of " + device, e);
+                }
+                if (btLeCodecStatus == null) {
+                    Log.i(TAG, "onBtProfileConnected null LE codec status for groupId: "
+                            + groupId + ", device: " + device);
+                    break;
+                }
+                List<BluetoothLeAudioCodecConfig> outputCodecConfigs =
+                        btLeCodecStatus.getOutputCodecSelectableCapabilities();
+                if (!outputCodecConfigs.isEmpty()) {
+                    BluetoothProfileConnectionInfo bpci =
+                            BluetoothProfileConnectionInfo.createLeAudioInfo(
+                                    false /*suppressNoisyIntent*/, true /*isLeOutput*/);
+                    postBluetoothActiveDevice(device, bpci);
+                }
+                List<BluetoothLeAudioCodecConfig> inputCodecConfigs =
+                        btLeCodecStatus.getInputCodecSelectableCapabilities();
+                if (!inputCodecConfigs.isEmpty()) {
+                    BluetoothProfileConnectionInfo bpci =
+                            BluetoothProfileConnectionInfo.createLeAudioInfo(
+                                    false /*suppressNoisyIntent*/, false /*isLeOutput*/);
+                    postBluetoothActiveDevice(device, bpci);
+                }
+            } break;
+            default:
+                // Not a valid profile to connect
+                Log.wtf(TAG, "Invalid profile! onBtProfileConnected");
+                break;
+        }
+    }
+
+    private void postBluetoothActiveDevice(
+            BluetoothDevice device, BluetoothProfileConnectionInfo bpci) {
+        AudioDeviceBroker.BtDeviceChangedData data = new AudioDeviceBroker.BtDeviceChangedData(
+                device, null, bpci, "mBluetoothProfileServiceListener");
+        AudioDeviceBroker.BtDeviceInfo info = mDeviceBroker.createBtDeviceInfo(
+                data, device, BluetoothProfile.STATE_CONNECTED);
+        mDeviceBroker.postBluetoothActiveDevice(info, 0 /* delay */);
     }
 
     /*package*/ synchronized boolean isProfilePoxyConnected(int profile) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
index d64b6c2..8dc560b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
@@ -20,6 +20,9 @@
 import android.content.Context;
 import android.hardware.biometrics.ITestSession;
 import android.hardware.biometrics.ITestSessionCallback;
+import android.hardware.biometrics.fingerprint.AcquiredInfoAndVendorCode;
+import android.hardware.biometrics.fingerprint.EnrollmentProgressStep;
+import android.hardware.biometrics.fingerprint.NextEnrollment;
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintEnrollOptions;
 import android.hardware.fingerprint.FingerprintManager;
@@ -46,6 +49,7 @@
 class BiometricTestSessionImpl extends ITestSession.Stub {
 
     private static final String TAG = "fp/aidl/BiometricTestSessionImpl";
+    private static final int VHAL_ENROLLMENT_ID = 9999;
 
     @NonNull private final Context mContext;
     private final int mSensorId;
@@ -140,8 +144,8 @@
 
         super.setTestHalEnabled_enforcePermission();
 
-        mProvider.setTestHalEnabled(enabled);
         mSensor.setTestHalEnabled(enabled);
+        mProvider.setTestHalEnabled(enabled);
     }
 
     @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
@@ -157,10 +161,31 @@
 
     @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
     @Override
-    public void finishEnroll(int userId) {
+    public void finishEnroll(int userId) throws RemoteException {
 
         super.finishEnroll_enforcePermission();
 
+        Slog.i(TAG, "finishEnroll(): useVhalForTesting=" + mProvider.useVhalForTesting());
+        if (mProvider.useVhalForTesting()) {
+            final AcquiredInfoAndVendorCode[] acquiredInfoAndVendorCodes =
+                    {new AcquiredInfoAndVendorCode()};
+            final EnrollmentProgressStep[] enrollmentProgressSteps =
+                    {new EnrollmentProgressStep(), new EnrollmentProgressStep()};
+            enrollmentProgressSteps[0].durationMs = 100;
+            enrollmentProgressSteps[0].acquiredInfoAndVendorCodes = acquiredInfoAndVendorCodes;
+            enrollmentProgressSteps[1].durationMs = 200;
+            enrollmentProgressSteps[1].acquiredInfoAndVendorCodes = acquiredInfoAndVendorCodes;
+
+            final NextEnrollment nextEnrollment = new NextEnrollment();
+            nextEnrollment.id = VHAL_ENROLLMENT_ID;
+            nextEnrollment.progressSteps = enrollmentProgressSteps;
+            nextEnrollment.result = true;
+            mProvider.getVhal().setNextEnrollment(nextEnrollment);
+            mProvider.simulateVhalFingerDown(userId, mSensorId);
+            return;
+        }
+
+        //TODO (b341889971): delete the following lines when b/341889971 is resolved
         int nextRandomId = mRandom.nextInt();
         while (mEnrollmentIds.contains(nextRandomId)) {
             nextRandomId = mRandom.nextInt();
@@ -173,11 +198,18 @@
 
     @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
     @Override
-    public void acceptAuthentication(int userId)  {
+    public void acceptAuthentication(int userId) throws RemoteException {
 
         // Fake authentication with any of the existing fingers
         super.acceptAuthentication_enforcePermission();
 
+        if (mProvider.useVhalForTesting()) {
+            mProvider.getVhal().setEnrollmentHit(VHAL_ENROLLMENT_ID);
+            mProvider.simulateVhalFingerDown(userId, mSensorId);
+            return;
+        }
+
+        //TODO (b341889971): delete the following lines when b/341889971 is resolved
         List<Fingerprint> fingerprints = FingerprintUtils.getInstance(mSensorId)
                 .getBiometricsForUser(mContext, userId);
         if (fingerprints.isEmpty()) {
@@ -191,10 +223,17 @@
 
     @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
     @Override
-    public void rejectAuthentication(int userId)  {
+    public void rejectAuthentication(int userId) throws RemoteException  {
 
         super.rejectAuthentication_enforcePermission();
 
+        if (mProvider.useVhalForTesting()) {
+            mProvider.getVhal().setEnrollmentHit(VHAL_ENROLLMENT_ID + 1);
+            mProvider.simulateVhalFingerDown(userId, mSensorId);
+            return;
+        }
+
+        //TODO (b341889971): delete the following lines when b/341889971 is resolved
         mSensor.getSessionForUser(userId).getHalSessionCallback().onAuthenticationFailed();
     }
 
@@ -220,11 +259,17 @@
 
     @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
     @Override
-    public void cleanupInternalState(int userId)  {
+    public void cleanupInternalState(int userId) throws RemoteException {
 
         super.cleanupInternalState_enforcePermission();
 
         Slog.d(TAG, "cleanupInternalState: " + userId);
+
+        if (mProvider.useVhalForTesting()) {
+            Slog.i(TAG, "cleanup virtualhal configurations");
+            mProvider.getVhal().resetConfigurations(); //setEnrollments(new int[]{});
+        }
+
         mProvider.scheduleInternalCleanup(mSensorId, userId, new ClientMonitorCallback() {
             @Override
             public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
@@ -248,4 +293,4 @@
             }
         });
     }
-}
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index c0dcd49..1bddb83b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -890,7 +890,13 @@
     }
 
     void setTestHalEnabled(boolean enabled) {
+        final boolean changed = enabled != mTestHalEnabled;
         mTestHalEnabled = enabled;
+        Slog.i(getTag(), "setTestHalEnabled(): useVhalForTesting=" + Flags.useVhalForTesting()
+                + "mTestHalEnabled=" + mTestHalEnabled + " changed=" + changed);
+        if (changed && useVhalForTesting()) {
+            getHalInstance();
+        }
     }
 
     public boolean getTestHalEnabled() {
@@ -982,7 +988,7 @@
         if (mVhal == null && useVhalForTesting()) {
             mVhal = IVirtualHal.Stub.asInterface(mDaemon.asBinder().getExtension());
             if (mVhal == null) {
-                Slog.e(getTag(), "Unable to get virtual hal interface");
+                Slog.e(getTag(), "Unable to get fingerprint virtualhal interface");
             }
         }
 
diff --git a/services/core/java/com/android/server/display/DisplayControl.java b/services/core/java/com/android/server/display/DisplayControl.java
index fa8299b..38eb416 100644
--- a/services/core/java/com/android/server/display/DisplayControl.java
+++ b/services/core/java/com/android/server/display/DisplayControl.java
@@ -28,9 +28,9 @@
  * Calls into SurfaceFlinger for Display creation and deletion.
  */
 public class DisplayControl {
-    private static native IBinder nativeCreateDisplay(String name, boolean secure,
+    private static native IBinder nativeCreateVirtualDisplay(String name, boolean secure,
             String uniqueId, float requestedRefreshRate);
-    private static native void nativeDestroyDisplay(IBinder displayToken);
+    private static native void nativeDestroyVirtualDisplay(IBinder displayToken);
     private static native void nativeOverrideHdrTypes(IBinder displayToken, int[] modes);
     private static native long[] nativeGetPhysicalDisplayIds();
     private static native IBinder nativeGetPhysicalDisplayToken(long physicalDisplayId);
@@ -41,21 +41,21 @@
     private static native boolean nativeGetHdrOutputConversionSupport();
 
     /**
-     * Create a display in SurfaceFlinger.
+     * Create a virtual display in SurfaceFlinger.
      *
-     * @param name The name of the display.
+     * @param name The name of the virtual display.
      * @param secure Whether this display is secure.
      * @return The token reference for the display in SurfaceFlinger.
      */
-    public static IBinder createDisplay(String name, boolean secure) {
+    public static IBinder createVirtualDisplay(String name, boolean secure) {
         Objects.requireNonNull(name, "name must not be null");
-        return nativeCreateDisplay(name, secure, "", 0.0f);
+        return nativeCreateVirtualDisplay(name, secure, "", 0.0f);
     }
 
     /**
-     * Create a display in SurfaceFlinger.
+     * Create a virtual display in SurfaceFlinger.
      *
-     * @param name The name of the display.
+     * @param name The name of the virtual display.
      * @param secure Whether this display is secure.
      * @param uniqueId The unique ID for the display.
      * @param requestedRefreshRate The requested refresh rate in frames per second.
@@ -65,23 +65,23 @@
      * display is refreshed at the physical display refresh rate.
      * @return The token reference for the display in SurfaceFlinger.
      */
-    public static IBinder createDisplay(String name, boolean secure,
+    public static IBinder createVirtualDisplay(String name, boolean secure,
             String uniqueId, float requestedRefreshRate) {
         Objects.requireNonNull(name, "name must not be null");
         Objects.requireNonNull(uniqueId, "uniqueId must not be null");
-        return nativeCreateDisplay(name, secure, uniqueId, requestedRefreshRate);
+        return nativeCreateVirtualDisplay(name, secure, uniqueId, requestedRefreshRate);
     }
 
     /**
-     * Destroy a display in SurfaceFlinger.
+     * Destroy a virtual display in SurfaceFlinger.
      *
-     * @param displayToken The display token for the display to be destroyed.
+     * @param displayToken The display token for the virtual display to be destroyed.
      */
-    public static void destroyDisplay(IBinder displayToken) {
+    public static void destroyVirtualDisplay(IBinder displayToken) {
         if (displayToken == null) {
             throw new IllegalArgumentException("displayToken must not be null");
         }
-        nativeDestroyDisplay(displayToken);
+        nativeDestroyVirtualDisplay(displayToken);
     }
 
     /**
diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
index 22ff2d0..eb76dcb 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -309,7 +309,7 @@
                 mSurface.release();
                 mSurface = null;
             }
-            DisplayControl.destroyDisplay(getDisplayTokenLocked());
+            DisplayControl.destroyVirtualDisplay(getDisplayTokenLocked());
         }
 
         @Override
@@ -467,7 +467,7 @@
         public void onWindowCreated(SurfaceTexture surfaceTexture, float refreshRate,
                 long presentationDeadlineNanos, int state) {
             synchronized (getSyncRoot()) {
-                IBinder displayToken = DisplayControl.createDisplay(mName, mFlags.mSecure);
+                IBinder displayToken = DisplayControl.createVirtualDisplay(mName, mFlags.mSecure);
                 mDevice = new OverlayDisplayDevice(displayToken, mName, mModes, mActiveMode,
                         DEFAULT_MODE_INDEX, refreshRate, presentationDeadlineNanos,
                         mFlags, state, surfaceTexture, mNumber) {
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index a29e852..1a5c79f 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -94,12 +94,13 @@
             @Override
             public IBinder createDisplay(String name, boolean secure, String uniqueId,
                                          float requestedRefreshRate) {
-                return DisplayControl.createDisplay(name, secure, uniqueId, requestedRefreshRate);
+                return DisplayControl.createVirtualDisplay(name, secure, uniqueId,
+                                                           requestedRefreshRate);
             }
 
             @Override
             public void destroyDisplay(IBinder displayToken) {
-                DisplayControl.destroyDisplay(displayToken);
+                DisplayControl.destroyVirtualDisplay(displayToken);
             }
         }, featureFlags);
     }
diff --git a/services/core/java/com/android/server/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
index aa98cd8..607c5d6 100644
--- a/services/core/java/com/android/server/display/WifiDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
@@ -392,9 +392,9 @@
 
         float refreshRate = 60.0f; // TODO: get this for real
 
-        String name = display.getFriendlyDisplayName();
-        String address = display.getDeviceAddress();
-        IBinder displayToken = DisplayControl.createDisplay(name, secure);
+        final String name = display.getFriendlyDisplayName();
+        final String address = display.getDeviceAddress();
+        IBinder displayToken = DisplayControl.createVirtualDisplay(name, secure);
         mDisplayDevice = new WifiDisplayDevice(displayToken, name, width, height,
                 refreshRate, deviceFlags, address, surface);
         sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_ADDED);
@@ -631,7 +631,7 @@
                 mSurface.release();
                 mSurface = null;
             }
-            DisplayControl.destroyDisplay(getDisplayTokenLocked());
+            DisplayControl.destroyVirtualDisplay(getDisplayTokenLocked());
         }
 
         public void setNameLocked(String name) {
diff --git a/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java b/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java
index ad98b4a..a3b1a2d 100644
--- a/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java
+++ b/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java
@@ -102,26 +102,33 @@
             boolean touchExplorationEnabled) {
         clearPendingInlineSuggestionsRequest();
         mInlineSuggestionsRequestCallback = callback;
-        final InputMethodInfo imi = mService.queryInputMethodForCurrentUserLocked(
-                mService.getSelectedMethodIdLocked());
 
-        if (userId == mService.getCurrentImeUserIdLocked()
-                && imi != null && isInlineSuggestionsEnabled(imi, touchExplorationEnabled)) {
-            mPendingInlineSuggestionsRequest = new CreateInlineSuggestionsRequest(
-                    requestInfo, callback, imi.getPackageName());
-            if (mService.getCurMethodLocked() != null) {
-                // In the normal case when the IME is connected, we can make the request here.
-                performOnCreateInlineSuggestionsRequest();
-            } else {
-                // Otherwise, the next time the IME connection is established,
-                // InputMethodBindingController.mMainConnection#onServiceConnected() will call
-                // into #performOnCreateInlineSuggestionsRequestLocked() to make the request.
-                if (DEBUG) {
-                    Slog.d(TAG, "IME not connected. Delaying inline suggestions request.");
-                }
-            }
-        } else {
+        if (userId != mService.getCurrentImeUserIdLocked()) {
             callback.onInlineSuggestionsUnsupported();
+            return;
+        }
+
+        // Note that current user ID is guaranteed to be userId.
+        final var imeId = mService.getSelectedMethodIdLocked();
+        final InputMethodInfo imi = InputMethodSettingsRepository.get(userId).getMethodMap()
+                .get(imeId);
+        if (imi == null || !isInlineSuggestionsEnabled(imi, touchExplorationEnabled)) {
+            callback.onInlineSuggestionsUnsupported();
+            return;
+        }
+
+        mPendingInlineSuggestionsRequest = new CreateInlineSuggestionsRequest(
+                requestInfo, callback, imi.getPackageName());
+        if (mService.getCurMethodLocked() != null) {
+            // In the normal case when the IME is connected, we can make the request here.
+            performOnCreateInlineSuggestionsRequest();
+        } else {
+            // Otherwise, the next time the IME connection is established,
+            // InputMethodBindingController.mMainConnection#onServiceConnected() will call
+            // into #performOnCreateInlineSuggestionsRequestLocked() to make the request.
+            if (DEBUG) {
+                Slog.d(TAG, "IME not connected. Delaying inline suggestions request.");
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 7548b36..f45b82a 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -491,6 +491,12 @@
      */
     boolean mSystemReady;
 
+    @GuardedBy("ImfLock.class")
+    @NonNull
+    InputMethodBindingController getInputMethodBindingController(@UserIdInt int userId) {
+        return mUserDataRepository.getOrCreate(userId).mBindingController;
+    }
+
     /**
      * Id obtained with {@link InputMethodInfo#getId()} for the currently selected input method.
      * This is to be synchronized with the secure settings keyed with
@@ -507,8 +513,7 @@
     @GuardedBy("ImfLock.class")
     @Nullable
     String getSelectedMethodIdLocked() {
-        final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-        return userData.mBindingController.getSelectedMethodId();
+        return getInputMethodBindingController(mCurrentUserId).getSelectedMethodId();
     }
 
     @GuardedBy("ImfLock.class")
@@ -594,8 +599,7 @@
     @GuardedBy("ImfLock.class")
     @Nullable
     IBinder getCurTokenLocked() {
-        final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-        return userData.mBindingController.getCurToken();
+        return getInputMethodBindingController(mCurrentUserId).getCurToken();
     }
 
     /**
@@ -603,8 +607,7 @@
      */
     @GuardedBy("ImfLock.class")
     int getCurTokenDisplayIdLocked() {
-        final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-        return userData.mBindingController.getCurTokenDisplayId();
+        return getInputMethodBindingController(mCurrentUserId).getCurTokenDisplayId();
     }
 
     /**
@@ -620,8 +623,7 @@
     @GuardedBy("ImfLock.class")
     @Nullable
     IInputMethodInvoker getCurMethodLocked() {
-        final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-        return userData.mBindingController.getCurMethod();
+        return getInputMethodBindingController(mCurrentUserId).getCurMethod();
     }
 
     /**
@@ -1127,6 +1129,11 @@
         public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
             // Called on ActivityManager thread.
             synchronized (ImfLock.class) {
+                if (mService.mExperimentalConcurrentMultiUserModeEnabled) {
+                    // In concurrent multi-user mode, we in general do not rely on the concept of
+                    // current user.
+                    return;
+                }
                 mService.scheduleSwitchUserTaskLocked(to.getUserIdentifier(),
                         /* clientToBeReset= */ null);
             }
@@ -1269,7 +1276,15 @@
             InputMethodSettingsRepository.initialize(mHandler, mContext);
             AdditionalSubtypeMapRepository.initialize(mHandler, mContext);
 
-            mCurrentUserId = mActivityManagerInternal.getCurrentUserId();
+            final int currentUserId = mActivityManagerInternal.getCurrentUserId();
+
+            // For concurrent multi-user mode, we try to initialize mCurrentUserId with main
+            // user rather than the current user when possible.
+            mCurrentUserId = mExperimentalConcurrentMultiUserModeEnabled
+                    ? MultiUserUtils.getFirstMainUserIdOrDefault(
+                            mUserManagerInternal, currentUserId)
+                    : currentUserId;
+
             @SuppressWarnings("GuardedBy") final IntFunction<InputMethodBindingController>
                     bindingControllerFactory = userId -> new InputMethodBindingController(userId,
                     InputMethodManagerService.this);
@@ -1422,8 +1437,7 @@
 
         // Note that in b/197848765 we want to see if we can keep the binding alive for better
         // profile switching.
-        final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-        final var bindingController = userData.mBindingController;
+        final var bindingController = getInputMethodBindingController(mCurrentUserId);
         bindingController.unbindCurrentMethod();
 
         unbindCurrentClientLocked(UnbindReason.SWITCH_USER);
@@ -1643,8 +1657,7 @@
 
             // Check if selected IME of current user supports handwriting.
             if (userId == mCurrentUserId) {
-                final var userData = mUserDataRepository.getOrCreate(userId);
-                final var bindingController = userData.mBindingController;
+                final var bindingController = getInputMethodBindingController(userId);
                 return bindingController.supportsStylusHandwriting()
                         && (!connectionless
                         || bindingController.supportsConnectionlessStylusHandwriting());
@@ -1844,8 +1857,7 @@
             // TODO(b/325515685): make binding controller user independent. Before this change, the
             //  following dependencies also need to be user independent: mCurClient, mBoundToMethod,
             //  getCurMethodLocked(), and mMenuController.
-            final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-            final var bindingController = userData.mBindingController;
+            final var bindingController = getInputMethodBindingController(mCurrentUserId);
             mCurClient.mClient.onUnbindMethod(bindingController.getSequenceNumber(),
                     unbindClientReason);
             mCurClient.mSessionRequested = false;
@@ -1925,8 +1937,7 @@
 
         final boolean restarting = !initial;
         final Binder startInputToken = new Binder();
-        final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-        final var bindingController = userData.mBindingController;
+        final var bindingController = getInputMethodBindingController(mCurrentUserId);
         final StartInputInfo info = new StartInputInfo(mCurrentUserId,
                 getCurTokenLocked(),
                 getCurTokenDisplayIdLocked(), bindingController.getCurId(), startInputReason,
@@ -2032,7 +2043,7 @@
             @StartInputReason int startInputReason,
             int unverifiedTargetSdkVersion,
             @NonNull ImeOnBackInvokedDispatcher imeDispatcher,
-            @NonNull UserDataRepository.UserData userData) {
+            @NonNull InputMethodBindingController bindingController) {
 
         // Compute the final shown display ID with validated cs.selfReportedDisplayId for this
         // session & other conditions.
@@ -2073,7 +2084,6 @@
         final boolean connectionWasActive = mCurInputConnection != null;
 
         // Bump up the sequence for this client and attach it.
-        final var bindingController = userData.mBindingController;
         bindingController.advanceSequenceNumber();
 
         mCurClient = cs;
@@ -2133,7 +2143,7 @@
                         (startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0);
             }
 
-            InputBindResult bindResult = tryReuseConnectionLocked(userData, cs);
+            InputBindResult bindResult = tryReuseConnectionLocked(bindingController, cs);
             if (bindResult != null) {
                 return bindResult;
             }
@@ -2247,9 +2257,8 @@
 
     @GuardedBy("ImfLock.class")
     @Nullable
-    private InputBindResult tryReuseConnectionLocked(@NonNull UserDataRepository.UserData userData,
-            @NonNull ClientState cs) {
-        final var bindingController = userData.mBindingController;
+    private InputBindResult tryReuseConnectionLocked(
+            @NonNull InputMethodBindingController bindingController, @NonNull ClientState cs) {
         if (bindingController.hasMainConnection()) {
             if (getCurMethodLocked() != null) {
                 // Return to client, and we will get back with it when
@@ -2631,8 +2640,9 @@
         // When the IME switcher dialog is shown, the IME switcher button should be hidden.
         if (mMenuController.getSwitchingDialogLocked() != null) return false;
         // When we are switching IMEs, the IME switcher button should be hidden.
-        final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-        if (!Objects.equals(userData.mBindingController.getCurId(), getSelectedMethodIdLocked())) {
+        final var bindingController = getInputMethodBindingController(mCurrentUserId);
+        if (!Objects.equals(bindingController.getCurId(),
+                bindingController.getSelectedMethodId())) {
             return false;
         }
         if (mWindowManagerInternal.isKeyguardShowingAndNotOccluded()
@@ -2794,8 +2804,7 @@
             } else {
                 vis &= ~InputMethodService.IME_VISIBLE_IMPERCEPTIBLE;
             }
-            final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-            final var curId = userData.mBindingController.getCurId();
+            final var curId = getInputMethodBindingController(mCurrentUserId).getCurId();
             if (mMenuController.getSwitchingDialogLocked() != null
                     || !Objects.equals(curId, getSelectedMethodIdLocked())) {
                 // When the IME switcher dialog is shown, or we are switching IMEs,
@@ -2856,8 +2865,7 @@
             id = imi.getId();
             settings.putSelectedInputMethod(id);
         }
-        final var userData = mUserDataRepository.getOrCreate(userId);
-        final var bindingController = userData.mBindingController;
+        final var bindingController = getInputMethodBindingController(userId);
         bindingController.setSelectedMethodId(id);
     }
 
@@ -3033,8 +3041,7 @@
             // mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked()
             // because mCurMethodId is stored as a history in
             // setSelectedInputMethodAndSubtypeLocked().
-            final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-            userData.mBindingController.setSelectedMethodId(id);
+            getInputMethodBindingController(mCurrentUserId).setSelectedMethodId(id);
 
             if (mActivityManagerInternal.isSystemReady()) {
                 Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
@@ -3089,8 +3096,8 @@
             @Nullable String delegatorPackageName,
             @NonNull IConnectionlessHandwritingCallback callback) {
         synchronized (ImfLock.class) {
-            final var userData = mUserDataRepository.getOrCreate(userId);
-            if (!userData.mBindingController.supportsConnectionlessStylusHandwriting()) {
+            final var bindingController = getInputMethodBindingController(userId);
+            if (!bindingController.supportsConnectionlessStylusHandwriting()) {
                 Slog.w(TAG, "Connectionless stylus handwriting mode unsupported by IME.");
                 try {
                     callback.onError(CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED);
@@ -3173,8 +3180,8 @@
                 }
                 final long ident = Binder.clearCallingIdentity();
                 try {
-                    final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-                    if (!userData.mBindingController.supportsStylusHandwriting()) {
+                    final var bindingController = getInputMethodBindingController(mCurrentUserId);
+                    if (!bindingController.supportsStylusHandwriting()) {
                         Slog.w(TAG,
                                 "Stylus HW unsupported by IME. Ignoring startStylusHandwriting()");
                         return false;
@@ -3357,8 +3364,8 @@
         mVisibilityStateComputer.requestImeVisibility(windowToken, true);
 
         // Ensure binding the connection when IME is going to show.
-        final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-        userData.mBindingController.setCurrentMethodVisible();
+        final var bindingController = getInputMethodBindingController(mCurrentUserId);
+        bindingController.setCurrentMethodVisible();
         final IInputMethodInvoker curMethod = getCurMethodLocked();
         ImeTracker.forLogging().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
         final boolean readyToDispatchToIme;
@@ -3466,8 +3473,8 @@
         } else {
             ImeTracker.forLogging().onCancelled(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE);
         }
-        final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-        userData.mBindingController.setCurrentMethodNotVisible();
+        final var bindingController = getInputMethodBindingController(mCurrentUserId);
+        bindingController.setCurrentMethodNotVisible();
         mVisibilityStateComputer.clearImeShowFlags();
         // Cancel existing statsToken for show IME as we got a hide request.
         ImeTracker.forLogging().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
@@ -3535,8 +3542,7 @@
                     "InputMethodManagerService#startInputOrWindowGainedFocus", mDumper);
             final InputBindResult result;
             synchronized (ImfLock.class) {
-                final var userData = mUserDataRepository.getOrCreate(userId);
-                final var bindingController = userData.mBindingController;
+                final var bindingController = getInputMethodBindingController(userId);
                 // If the system is not yet ready, we shouldn't be running third party code.
                 if (!mSystemReady) {
                     return new InputBindResult(
@@ -3553,7 +3559,8 @@
                 final long ident = Binder.clearCallingIdentity();
                 try {
                     // Verify if IMMS is in the process of switching user.
-                    if (mUserSwitchHandlerTask != null) {
+                    if (!mExperimentalConcurrentMultiUserModeEnabled
+                            && mUserSwitchHandlerTask != null) {
                         // There is already an on-going pending user switch task.
                         final int nextUserId = mUserSwitchHandlerTask.mToUserId;
                         if (userId == nextUserId) {
@@ -3607,7 +3614,7 @@
                     }
 
                     // Verify if caller is a background user.
-                    if (userId != mCurrentUserId) {
+                    if (!mExperimentalConcurrentMultiUserModeEnabled && userId != mCurrentUserId) {
                         if (ArrayUtils.contains(
                                 mUserManagerInternal.getProfileIds(mCurrentUserId, false),
                                 userId)) {
@@ -3635,7 +3642,7 @@
                     result = startInputOrWindowGainedFocusInternalLocked(startInputReason,
                             client, windowToken, startInputFlags, softInputMode, windowFlags,
                             editorInfo, inputConnection, remoteAccessibilityInputConnection,
-                            unverifiedTargetSdkVersion, userData, imeDispatcher, cs);
+                            unverifiedTargetSdkVersion, bindingController, imeDispatcher, cs);
                 } finally {
                     Binder.restoreCallingIdentity(ident);
                 }
@@ -3663,7 +3670,7 @@
             @SoftInputModeFlags int softInputMode, int windowFlags, EditorInfo editorInfo,
             IRemoteInputConnection inputContext,
             @Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
-            int unverifiedTargetSdkVersion, @NonNull UserDataRepository.UserData userData,
+            int unverifiedTargetSdkVersion, @NonNull InputMethodBindingController bindingController,
             @NonNull ImeOnBackInvokedDispatcher imeDispatcher, @NonNull ClientState cs) {
         if (DEBUG) {
             Slog.v(TAG, "startInputOrWindowGainedFocusInternalLocked: reason="
@@ -3676,7 +3683,7 @@
                     + " softInputMode=" + InputMethodDebug.softInputModeToString(softInputMode)
                     + " windowFlags=#" + Integer.toHexString(windowFlags)
                     + " unverifiedTargetSdkVersion=" + unverifiedTargetSdkVersion
-                    + " userData=" + userData
+                    + " bindingController=" + bindingController
                     + " imeDispatcher=" + imeDispatcher
                     + " cs=" + cs);
         }
@@ -3705,15 +3712,16 @@
             if (editorInfo != null) {
                 return startInputUncheckedLocked(cs, inputContext,
                         remoteAccessibilityInputConnection, editorInfo, startInputFlags,
-                        startInputReason, unverifiedTargetSdkVersion, imeDispatcher, userData);
+                        startInputReason, unverifiedTargetSdkVersion, imeDispatcher,
+                        bindingController);
             }
             return new InputBindResult(
                     InputBindResult.ResultCode.SUCCESS_REPORT_WINDOW_FOCUS_ONLY,
                     null, null, null, null, -1, false);
         }
 
-        mImeBindingState = new ImeBindingState(userData.mUserId, windowToken, softInputMode, cs,
-                editorInfo);
+        mImeBindingState = new ImeBindingState(bindingController.mUserId, windowToken,
+                softInputMode, cs, editorInfo);
         mFocusedWindowPerceptible.put(windowToken, true);
 
         // We want to start input before showing the IME, but after closing
@@ -3738,7 +3746,7 @@
                         res = startInputUncheckedLocked(cs, inputContext,
                                 remoteAccessibilityInputConnection, editorInfo, startInputFlags,
                                 startInputReason, unverifiedTargetSdkVersion,
-                                imeDispatcher, userData);
+                                imeDispatcher, bindingController);
                         didStart = true;
                     }
                     break;
@@ -3753,7 +3761,7 @@
                 // Note that we can trust client's display ID as long as it matches
                 // to the display ID obtained from the window.
                 if (cs.mSelfReportedDisplayId != getCurTokenDisplayIdLocked()) {
-                    userData.mBindingController.unbindCurrentMethod();
+                    bindingController.unbindCurrentMethod();
                 }
             }
         }
@@ -3762,7 +3770,7 @@
                 res = startInputUncheckedLocked(cs, inputContext,
                         remoteAccessibilityInputConnection, editorInfo, startInputFlags,
                         startInputReason, unverifiedTargetSdkVersion,
-                        imeDispatcher, userData);
+                        imeDispatcher, bindingController);
             } else {
                 res = InputBindResult.NULL_EDITOR_INFO;
             }
@@ -3803,8 +3811,7 @@
         if (mCurrentUserId != UserHandle.getUserId(uid)) {
             return false;
         }
-        final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-        final var curIntent = userData.mBindingController.getCurIntent();
+        final var curIntent = getInputMethodBindingController(mCurrentUserId).getCurIntent();
         if (curIntent != null && InputMethodUtils.checkIfPackageBelongsToUid(
                 mPackageManagerInternal, uid, curIntent.getComponent().getPackageName())) {
             return true;
@@ -4213,8 +4220,7 @@
         mStylusIds.add(deviceId);
         // a new Stylus is detected. If IME supports handwriting, and we don't have
         // handwriting initialized, lets do it now.
-        final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-        final var bindingController = userData.mBindingController;
+        final var bindingController = getInputMethodBindingController(mCurrentUserId);
         if (!mHwController.getCurrentRequestId().isPresent()
                 && bindingController.supportsStylusHandwriting()) {
             scheduleResetStylusHandwriting();
@@ -4395,8 +4401,7 @@
 
     private void dumpDebug(ProtoOutputStream proto, long fieldId) {
         synchronized (ImfLock.class) {
-            final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-            final var bindingController = userData.mBindingController;
+            final var bindingController = getInputMethodBindingController(mCurrentUserId);
             final long token = proto.start(fieldId);
             proto.write(CUR_METHOD_ID, getSelectedMethodIdLocked());
             proto.write(CUR_SEQ, bindingController.getSequenceNumber());
@@ -4786,8 +4791,7 @@
 
             case MSG_RESET_HANDWRITING: {
                 synchronized (ImfLock.class) {
-                    final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-                    final var bindingController = userData.mBindingController;
+                    final var bindingController = getInputMethodBindingController(mCurrentUserId);
                     if (bindingController.supportsStylusHandwriting()
                             && getCurMethodLocked() != null && hasSupportedStylusLocked()) {
                         Slog.d(TAG, "Initializing Handwriting Spy");
@@ -4813,8 +4817,7 @@
                     if (curMethod == null || mImeBindingState.mFocusedWindow == null) {
                         return true;
                     }
-                    final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-                    final var bindingController = userData.mBindingController;
+                    final var bindingController = getInputMethodBindingController(mCurrentUserId);
                     final HandwritingModeController.HandwritingSession session =
                             mHwController.startHandwritingSession(
                                     msg.arg1 /*requestId*/,
@@ -4870,8 +4873,7 @@
                 return;
             }
             // TODO(b/325515685): user data must be retrieved by a userId parameter
-            final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-            final var bindingController = userData.mBindingController;
+            final var bindingController = getInputMethodBindingController(mCurrentUserId);
             if (mImePlatformCompatUtils.shouldUseSetInteractiveProtocol(
                     bindingController.getCurMethodUid())) {
                 // Handle IME visibility when interactive changed before finishing the input to
@@ -5096,8 +5098,7 @@
 
     @GuardedBy("ImfLock.class")
     void sendOnNavButtonFlagsChangedLocked() {
-        final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-        final var bindingController = userData.mBindingController;
+        final var bindingController = getInputMethodBindingController(mCurrentUserId);
         final IInputMethodInvoker curMethod = bindingController.getCurMethod();
         if (curMethod == null) {
             // No need to send the data if the IME is not yet bound.
@@ -5584,8 +5585,7 @@
         public void onSessionForAccessibilityCreated(int accessibilityConnectionId,
                 IAccessibilityInputMethodSession session, @UserIdInt int userId) {
             synchronized (ImfLock.class) {
-                final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-                final var bindingController = userData.mBindingController;
+                final var bindingController = getInputMethodBindingController(mCurrentUserId);
                 // TODO(b/305829876): Implement user ID verification
                 if (mCurClient != null) {
                     clearClientSessionForAccessibilityLocked(mCurClient, accessibilityConnectionId);
@@ -5620,8 +5620,7 @@
         public void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId,
                 @UserIdInt int userId) {
             synchronized (ImfLock.class) {
-                final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-                final var bindingController = userData.mBindingController;
+                final var bindingController = getInputMethodBindingController(mCurrentUserId);
                 // TODO(b/305829876): Implement user ID verification
                 if (mCurClient != null) {
                     if (DEBUG) {
@@ -5853,8 +5852,7 @@
                 p.println("    pid=" + c.mPid);
             };
             mClientController.forAllClients(clientControllerDump);
-            final var userData = mUserDataRepository.getOrCreate(mCurrentUserId);
-            final var bindingController = userData.mBindingController;
+            final var bindingController = getInputMethodBindingController(mCurrentUserId);
             p.println("  mCurrentUserId=" + mCurrentUserId);
             p.println("  mCurMethodId=" + getSelectedMethodIdLocked());
             client = mCurClient;
@@ -6376,8 +6374,7 @@
                     if (userId == mCurrentUserId) {
                         hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
                                 SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND);
-                        final var userData = mUserDataRepository.getOrCreate(userId);
-                        final var bindingController = userData.mBindingController;
+                        final var bindingController = getInputMethodBindingController(userId);
                         bindingController.unbindCurrentMethod();
 
                         // Enable default IMEs, disable others
diff --git a/services/core/java/com/android/server/inputmethod/MultiUserUtils.java b/services/core/java/com/android/server/inputmethod/MultiUserUtils.java
new file mode 100644
index 0000000..8e188da
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/MultiUserUtils.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+
+import com.android.server.pm.UserManagerInternal;
+
+final class MultiUserUtils {
+    /**
+     * Not intended to be instantiated.
+     */
+    private MultiUserUtils() {
+    }
+
+    /**
+     * Return the first user ID (a user has {@link android.content.pm.UserInfo#FLAG_MAIN} if
+     * available). Otherwise, return the given default value.
+     *
+     * @param userManagerInternal {@link UserManagerInternal} to be used to query about users
+     * @param defaultValue a user ID that will be returned when there is no main user
+     * @return The first main user ID
+     */
+    @AnyThread
+    @UserIdInt
+    static int getFirstMainUserIdOrDefault(@NonNull UserManagerInternal userManagerInternal,
+            @UserIdInt int defaultValue) {
+        final int[] userIds = userManagerInternal.getUserIds();
+        if (userIds != null) {
+            for (int userId : userIds) {
+                final var userInfo = userManagerInternal.getUserInfo(userId);
+                if (userInfo.isMain()) {
+                    return userId;
+                }
+            }
+        }
+        return defaultValue;
+    }
+}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index dbdb155..b14702d 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -737,12 +737,13 @@
                     !mUserManager.isQuietModeEnabled(userHandle)) {
                 // Only show notifications for managed profiles once their parent
                 // user is unlocked.
-                showEncryptionNotificationForProfile(userHandle, reason);
+                showEncryptionNotificationForProfile(userHandle, parent.getUserHandle(), reason);
             }
         }
     }
 
-    private void showEncryptionNotificationForProfile(UserHandle user, String reason) {
+    private void showEncryptionNotificationForProfile(UserHandle user, UserHandle parent,
+            String reason) {
         CharSequence title = getEncryptionNotificationTitle();
         CharSequence message = getEncryptionNotificationMessage();
         CharSequence detail = getEncryptionNotificationDetail();
@@ -759,8 +760,15 @@
 
         unlockIntent.setFlags(
                 Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-        PendingIntent intent = PendingIntent.getActivity(mContext, 0, unlockIntent,
-                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
+        PendingIntent intent;
+        if (android.app.admin.flags.Flags.hsumUnlockNotificationFix()) {
+            intent = PendingIntent.getActivityAsUser(mContext, 0, unlockIntent,
+                    PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED,
+                    null, parent);
+        } else {
+            intent = PendingIntent.getActivity(mContext, 0, unlockIntent,
+                    PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
+        }
 
         Slogf.d(TAG, "Showing encryption notification for user %d; reason: %s",
                 user.getIdentifier(), reason);
diff --git a/services/core/java/com/android/server/media/MediaShellCommand.java b/services/core/java/com/android/server/media/MediaShellCommand.java
index a20de31..bea71dc 100644
--- a/services/core/java/com/android/server/media/MediaShellCommand.java
+++ b/services/core/java/com/android/server/media/MediaShellCommand.java
@@ -16,6 +16,7 @@
 
 package com.android.server.media;
 
+import android.annotation.NonNull;
 import android.app.ActivityThread;
 import android.content.Context;
 import android.media.MediaMetadata;
@@ -247,7 +248,7 @@
         }
 
         @Override
-        public void onAudioInfoChanged(MediaController.PlaybackInfo info) {
+        public void onAudioInfoChanged(@NonNull MediaController.PlaybackInfo info) {
             mWriter.println("onAudioInfoChanged " + info);
         }
     }
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 71a7d0d..f07b710 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -17,6 +17,7 @@
 package com.android.server.os;
 
 import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled;
+import static android.app.admin.flags.Flags.onboardingConsentlessBugreports;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -31,6 +32,7 @@
 import android.os.Binder;
 import android.os.BugreportManager.BugreportCallback;
 import android.os.BugreportParams;
+import android.os.Build;
 import android.os.Environment;
 import android.os.IDumpstate;
 import android.os.IDumpstateListener;
@@ -69,12 +71,14 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.PrintWriter;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.OptionalInt;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Implementation of the service that provides a privileged API to capture and consume bugreports.
@@ -98,6 +102,9 @@
     private static final String BUGREPORT_SERVICE = "bugreportd";
     private static final long DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS = 30 * 1000;
 
+    private static final long DEFAULT_BUGREPORT_CONSENTLESS_GRACE_PERIOD_MILLIS =
+            TimeUnit.MINUTES.toMillis(2);
+
     private final Object mLock = new Object();
     private final Injector mInjector;
     private final Context mContext;
@@ -132,6 +139,10 @@
         private ArrayMap<Pair<Integer, String>, ArraySet<String>> mBugreportFiles =
                 new ArrayMap<>();
 
+        // Map of <CallerPackage, Pair<TimestampOfLastConsent, skipConsentForFullReport>>
+        @GuardedBy("mLock")
+        private Map<String, Pair<Long, Boolean>> mConsentGranted = new HashMap<>();
+
         @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
         @GuardedBy("mLock")
         final Set<String> mBugreportFilesToPersist = new HashSet<>();
@@ -238,6 +249,64 @@
             }
         }
 
+        /**
+         * Logs an entry with a timestamp of a consent being granted by the user to the calling
+         * {@code packageName}.
+         */
+        void logConsentGrantedForCaller(
+                String packageName, boolean consentGranted, boolean isDeferredReport) {
+            if (!onboardingConsentlessBugreports() || !Build.IS_DEBUGGABLE) {
+                return;
+            }
+            synchronized (mLock) {
+                // Adds an entry with the timestamp of the consent being granted by the user, and
+                // whether the consent can be skipped for a full bugreport, because a single
+                // consent can be used for multiple deferred reports but only one full report.
+                if (consentGranted) {
+                    mConsentGranted.put(packageName, new Pair<>(
+                            System.currentTimeMillis(),
+                            isDeferredReport));
+                } else if (!isDeferredReport) {
+                    if (!mConsentGranted.containsKey(packageName)) {
+                        Slog.e(TAG, "Previous consent from package: " + packageName + " should"
+                                + "have been logged.");
+                        return;
+                    }
+                    mConsentGranted.put(packageName, new Pair<>(
+                            mConsentGranted.get(packageName).first,
+                            /* second = */ false
+                    ));
+                }
+            }
+        }
+
+        /**
+         * Returns {@code true} if user consent be skippeb because a previous consens has been
+         * granted to the caller within the allowed time period.
+         */
+        boolean canSkipConsentScreen(String packageName, boolean isFullReport) {
+            if (!onboardingConsentlessBugreports() || !Build.IS_DEBUGGABLE) {
+                return false;
+            }
+            synchronized (mLock) {
+                if (!mConsentGranted.containsKey(packageName)) {
+                    return false;
+                }
+                long currentTime = System.currentTimeMillis();
+                long consentGrantedTime = mConsentGranted.get(packageName).first;
+                if (consentGrantedTime + DEFAULT_BUGREPORT_CONSENTLESS_GRACE_PERIOD_MILLIS
+                        < currentTime) {
+                    mConsentGranted.remove(packageName);
+                    return false;
+                }
+                boolean skipConsentForFullReport = mConsentGranted.get(packageName).second;
+                if (isFullReport && !skipConsentForFullReport) {
+                    return false;
+                }
+                return true;
+            }
+        }
+
         private void addBugreportMapping(Pair<Integer, String> caller, String bugreportFile) {
             synchronized (mLock) {
                 if (!mBugreportFiles.containsKey(caller)) {
@@ -418,7 +487,7 @@
     public void startBugreport(int callingUidUnused, String callingPackage,
             FileDescriptor bugreportFd, FileDescriptor screenshotFd,
             int bugreportMode, int bugreportFlags, IDumpstateListener listener,
-            boolean isScreenshotRequested) {
+            boolean isScreenshotRequested, boolean skipUserConsentUnused) {
         Objects.requireNonNull(callingPackage);
         Objects.requireNonNull(bugreportFd);
         Objects.requireNonNull(listener);
@@ -509,7 +578,8 @@
     @RequiresPermission(value = Manifest.permission.DUMP, conditional = true)
     public void retrieveBugreport(int callingUidUnused, String callingPackage, int userId,
             FileDescriptor bugreportFd, String bugreportFile,
-            boolean keepBugreportOnRetrievalUnused, IDumpstateListener listener) {
+            boolean keepBugreportOnRetrievalUnused, boolean skipUserConsentUnused,
+            IDumpstateListener listener) {
         int callingUid = Binder.getCallingUid();
         enforcePermission(callingPackage, callingUid, false);
 
@@ -540,9 +610,13 @@
                 return;
             }
 
+            boolean skipUserConsent = mBugreportFileManager.canSkipConsentScreen(
+                    callingPackage, /* isFullReport = */ false);
+
             // Wrap the listener so we can intercept binder events directly.
             DumpstateListener myListener = new DumpstateListener(listener, ds,
-                    new Pair<>(callingUid, callingPackage), /* reportFinishedFile= */ true);
+                    new Pair<>(callingUid, callingPackage), /* reportFinishedFile= */ true,
+                    !skipUserConsent, /* isDeferredReport = */ true);
 
             boolean keepBugreportOnRetrieval = false;
             if (onboardingBugreportV2Enabled()) {
@@ -553,7 +627,7 @@
             setCurrentDumpstateListenerLocked(myListener);
             try {
                 ds.retrieveBugreport(callingUid, callingPackage, userId, bugreportFd,
-                        bugreportFile, keepBugreportOnRetrieval, myListener);
+                        bugreportFile, keepBugreportOnRetrieval, skipUserConsent, myListener);
             } catch (RemoteException e) {
                 Slog.e(TAG, "RemoteException in retrieveBugreport", e);
             }
@@ -754,7 +828,7 @@
             }
         }
 
-        boolean reportFinishedFile =
+        boolean isDeferredConsentReport =
                 (bugreportFlags & BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT) != 0;
 
         boolean keepBugreportOnRetrieval =
@@ -766,14 +840,17 @@
             reportError(listener, IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR);
             return;
         }
-
+        boolean skipUserConsent = mBugreportFileManager.canSkipConsentScreen(
+                callingPackage, !isDeferredConsentReport);
         DumpstateListener myListener = new DumpstateListener(listener, ds,
-                new Pair<>(callingUid, callingPackage), reportFinishedFile,
-                keepBugreportOnRetrieval);
+                new Pair<>(callingUid, callingPackage),
+                /* reportFinishedFile = */ isDeferredConsentReport, keepBugreportOnRetrieval,
+                !isDeferredConsentReport && !skipUserConsent,
+                isDeferredConsentReport);
         setCurrentDumpstateListenerLocked(myListener);
         try {
             ds.startBugreport(callingUid, callingPackage, bugreportFd, screenshotFd, bugreportMode,
-                    bugreportFlags, myListener, isScreenshotRequested);
+                    bugreportFlags, myListener, isScreenshotRequested, skipUserConsent);
         } catch (RemoteException e) {
             // dumpstate service is already started now. We need to kill it to manage the
             // lifecycle correctly. If we don't subsequent callers will get
@@ -930,14 +1007,21 @@
         private boolean mDone;
         private boolean mKeepBugreportOnRetrieval;
 
+        private boolean mConsentGranted;
+
+        private boolean mIsDeferredReport;
+
         DumpstateListener(IDumpstateListener listener, IDumpstate ds,
-                Pair<Integer, String> caller, boolean reportFinishedFile) {
-            this(listener, ds, caller, reportFinishedFile, /* keepBugreportOnRetrieval= */ false);
+                Pair<Integer, String> caller, boolean reportFinishedFile,
+                boolean consentGranted, boolean isDeferredReport) {
+            this(listener, ds, caller, reportFinishedFile, /* keepBugreportOnRetrieval= */ false,
+                    consentGranted, isDeferredReport);
         }
 
         DumpstateListener(IDumpstateListener listener, IDumpstate ds,
                 Pair<Integer, String> caller, boolean reportFinishedFile,
-                boolean keepBugreportOnRetrieval) {
+                boolean keepBugreportOnRetrieval, boolean consentGranted,
+                boolean isDeferredReport) {
             if (DEBUG) {
                 Slogf.d(TAG, "Starting DumpstateListener(id=%d) for caller %s", mId, caller);
             }
@@ -946,6 +1030,8 @@
             mCaller = caller;
             mReportFinishedFile = reportFinishedFile;
             mKeepBugreportOnRetrieval = keepBugreportOnRetrieval;
+            mConsentGranted = consentGranted;
+            mIsDeferredReport = isDeferredReport;
             try {
                 mDs.asBinder().linkToDeath(this, 0);
             } catch (RemoteException e) {
@@ -985,6 +1071,8 @@
             } else if (DEBUG) {
                 Slog.d(TAG, "Not reporting finished file");
             }
+            mBugreportFileManager.logConsentGrantedForCaller(
+                    mCaller.second, mConsentGranted, mIsDeferredReport);
             mListener.onFinished(bugreportFile);
         }
 
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 472f228..6cfa09f 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -511,14 +511,13 @@
         // metadata file on the system image. Do not reset the path and source if this is the
         // case.
         if (pkgSetting.getAppMetadataFilePath() == null) {
-            File dir = new File(pkg.getPath());
+            String dir = pkg.getPath();
             if (pkgSetting.isSystem()) {
-                dir = new File(Environment.getDataDirectory(),
-                        "app-metadata/" + pkg.getPackageName());
+                dir = Environment.getDataDirectoryPath() + "/app-metadata/" + pkg.getPackageName();
             }
-            File appMetadataFile = new File(dir, APP_METADATA_FILE_NAME);
-            if (appMetadataFile.exists()) {
-                pkgSetting.setAppMetadataFilePath(appMetadataFile.getAbsolutePath());
+            String appMetadataFilePath = dir + "/" + APP_METADATA_FILE_NAME;
+            if (request.hasAppMetadataFile()) {
+                pkgSetting.setAppMetadataFilePath(appMetadataFilePath);
                 if (Flags.aslInApkAppMetadataSource()) {
                     pkgSetting.setAppMetadataSource(APP_METADATA_SOURCE_INSTALLER);
                 }
@@ -526,7 +525,7 @@
                 Map<String, PackageManager.Property> properties = pkg.getProperties();
                 if (properties.containsKey(PROPERTY_ANDROID_SAFETY_LABEL)) {
                     // ASL file extraction is done in post-install
-                    pkgSetting.setAppMetadataFilePath(appMetadataFile.getAbsolutePath());
+                    pkgSetting.setAppMetadataFilePath(appMetadataFilePath);
                     pkgSetting.setAppMetadataSource(APP_METADATA_SOURCE_APK);
                 }
             }
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 6d38517..8f51e36 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -167,6 +167,8 @@
 
     private int mInstallerUidForInstallExisting = INVALID_UID;
 
+    private final boolean mHasAppMetadataFileFromInstaller;
+
     // New install
     InstallRequest(InstallingSession params) {
         mUserId = params.getUser().getIdentifier();
@@ -185,6 +187,7 @@
         mSessionId = params.mSessionId;
         mRequireUserAction = params.mRequireUserAction;
         mPreVerifiedDomains = params.mPreVerifiedDomains;
+        mHasAppMetadataFileFromInstaller = params.mHasAppMetadataFile;
     }
 
     // Install existing package as user
@@ -203,6 +206,7 @@
         mAppId = appId;
         mInstallerUidForInstallExisting = installerUid;
         mSystem = isSystem;
+        mHasAppMetadataFileFromInstaller = false;
     }
 
     // addForInit
@@ -224,6 +228,7 @@
         mSessionId = -1;
         mRequireUserAction = USER_ACTION_UNSPECIFIED;
         mDisabledPs = disabledPs;
+        mHasAppMetadataFileFromInstaller = false;
     }
 
     @Nullable
@@ -371,6 +376,10 @@
         return PackageInstallerSession.isArchivedInstallation(getInstallFlags());
     }
 
+    public boolean hasAppMetadataFile() {
+        return mHasAppMetadataFileFromInstaller;
+    }
+
     @Nullable
     public String getRemovedPackage() {
         return mRemovedInfo != null ? mRemovedInfo.mRemovedPackage : null;
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index 4cbd3ad..b06c7cb 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -101,6 +101,7 @@
     final boolean mApplicationEnabledSettingPersistent;
     @Nullable
     final DomainSet mPreVerifiedDomains;
+    final boolean mHasAppMetadataFile;
 
     // For move install
     InstallingSession(OriginInfo originInfo, MoveInfo moveInfo, IPackageInstallObserver2 observer,
@@ -134,12 +135,14 @@
         mRequireUserAction = USER_ACTION_UNSPECIFIED;
         mApplicationEnabledSettingPersistent = false;
         mPreVerifiedDomains = null;
+        mHasAppMetadataFile = false;
     }
 
     InstallingSession(int sessionId, File stagedDir, IPackageInstallObserver2 observer,
             PackageInstaller.SessionParams sessionParams, InstallSource installSource,
             UserHandle user, SigningDetails signingDetails, int installerUid,
-            PackageLite packageLite, DomainSet preVerifiedDomains, PackageManagerService pm) {
+            PackageLite packageLite, DomainSet preVerifiedDomains, PackageManagerService pm,
+            boolean hasAppMetadatafile) {
         mPm = pm;
         mUser = user;
         mOriginInfo = OriginInfo.fromStagedFile(stagedDir);
@@ -168,6 +171,7 @@
         mRequireUserAction = sessionParams.requireUserAction;
         mApplicationEnabledSettingPersistent = sessionParams.applicationEnabledSettingPersistent;
         mPreVerifiedDomains = preVerifiedDomains;
+        mHasAppMetadataFile = hasAppMetadatafile;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 80a5f3a..57f6d27 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -601,6 +601,9 @@
     @GuardedBy("mLock")
     private String mSessionErrorMessage;
 
+    @GuardedBy("mLock")
+    private boolean mHasAppMetadataFile = false;
+
     @Nullable
     final StagedSession mStagedSession;
 
@@ -1814,7 +1817,7 @@
         assertCallerIsOwnerOrRoot();
         synchronized (mLock) {
             assertPreparedAndNotCommittedOrDestroyedLocked("getAppMetadataFd");
-            if (!getStagedAppMetadataFile().exists()) {
+            if (!mHasAppMetadataFile) {
                 return null;
             }
             try {
@@ -1827,9 +1830,11 @@
 
     @Override
     public void removeAppMetadata() {
-        File file = getStagedAppMetadataFile();
-        if (file.exists()) {
-            file.delete();
+        synchronized (mLock) {
+            if (mHasAppMetadataFile) {
+                getStagedAppMetadataFile().delete();
+                mHasAppMetadataFile = false;
+            }
         }
     }
 
@@ -1850,8 +1855,12 @@
             assertPreparedAndNotSealedLocked("openWriteAppMetadata");
         }
         try {
-            return doWriteInternal(APP_METADATA_FILE_NAME, /* offsetBytes= */ 0,
+            ParcelFileDescriptor fd = doWriteInternal(APP_METADATA_FILE_NAME, /* offsetBytes= */ 0,
                     /* lengthBytes= */ -1, null);
+            synchronized (mLock) {
+                mHasAppMetadataFile = true;
+            }
+            return fd;
         } catch (IOException e) {
             throw ExceptionUtils.wrap(e);
         }
@@ -2145,18 +2154,21 @@
             }
         }
 
-        File appMetadataFile = getStagedAppMetadataFile();
-        if (appMetadataFile.exists()) {
-            long sizeLimit = getAppMetadataSizeLimit();
-            if (appMetadataFile.length() > sizeLimit) {
-                appMetadataFile.delete();
-                throw new IllegalArgumentException(
-                        "App metadata size exceeds the maximum allowed limit of " + sizeLimit);
-            }
-            if (isIncrementalInstallation()) {
-                // Incremental requires stageDir to be empty so move the app metadata file to a
-                // temporary location and move back after commit.
-                appMetadataFile.renameTo(getTmpAppMetadataFile());
+        synchronized (mLock) {
+            if (mHasAppMetadataFile) {
+                File appMetadataFile = getStagedAppMetadataFile();
+                long sizeLimit = getAppMetadataSizeLimit();
+                if (appMetadataFile.length() > sizeLimit) {
+                    appMetadataFile.delete();
+                    mHasAppMetadataFile = false;
+                    throw new IllegalArgumentException(
+                            "App metadata size exceeds the maximum allowed limit of " + sizeLimit);
+                }
+                if (isIncrementalInstallation()) {
+                    // Incremental requires stageDir to be empty so move the app metadata file to a
+                    // temporary location and move back after commit.
+                    appMetadataFile.renameTo(getTmpAppMetadataFile());
+                }
             }
         }
 
@@ -3207,7 +3219,8 @@
 
         synchronized (mLock) {
             return new InstallingSession(sessionId, stageDir, localObserver, params, mInstallSource,
-                    user, mSigningDetails, mInstallerUid, mPackageLite, mPreVerifiedDomains, mPm);
+                    user, mSigningDetails, mInstallerUid, mPackageLite, mPreVerifiedDomains, mPm,
+                    mHasAppMetadataFile);
         }
     }
 
@@ -3445,9 +3458,14 @@
             }
         }
 
+        if (mHasAppMetadataFile && !getStagedAppMetadataFile().exists()) {
+            throw new PackageManagerException(INSTALL_FAILED_VERIFICATION_FAILURE,
+                    "App metadata file expected but not found in " + stageDir.getAbsolutePath());
+        }
+
         final List<ApkLite> addedFiles = getAddedApkLitesLocked();
         if (addedFiles.isEmpty()
-                && (removeSplitList.size() == 0 || getStagedAppMetadataFile().exists())) {
+                && (removeSplitList.size() == 0 || mHasAppMetadataFile)) {
             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                     TextUtils.formatSimple("Session: %d. No packages staged in %s", sessionId,
                           stageDir.getAbsolutePath()));
diff --git a/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
index ad2c3e8..3579246 100644
--- a/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
+++ b/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
@@ -225,7 +225,7 @@
             @NonNull TimeConfiguration requestedConfiguration, boolean bypassUserPolicyChecks) {
         Objects.requireNonNull(requestedConfiguration);
 
-        TimeCapabilitiesAndConfig capabilitiesAndConfig = getCurrentUserConfigurationInternal()
+        TimeCapabilitiesAndConfig capabilitiesAndConfig = getConfigurationInternal(userId)
                 .createCapabilitiesAndConfig(bypassUserPolicyChecks);
         TimeCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
         TimeConfiguration oldConfiguration = capabilitiesAndConfig.getConfiguration();
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
index dd3d512..80f1125 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -150,7 +150,7 @@
                 Rect landscapeCrop = getCrop(rotatedDisplaySize, bitmapSize, suggestedCrops, rtl);
                 landscapeCrop = noParallax(landscapeCrop, rotatedDisplaySize, bitmapSize, rtl);
                 // compute the crop on portrait at the center of the landscape crop
-                crop = getAdjustedCrop(landscapeCrop, bitmapSize, displaySize, false, rtl, ADD);
+                crop = getAdjustedCrop(landscapeCrop, bitmapSize, displaySize, false, ADD);
 
                 // add some parallax (until the border of the landscape crop without parallax)
                 if (rtl) {
@@ -160,7 +160,7 @@
                 }
             }
 
-            return getAdjustedCrop(crop, bitmapSize, displaySize, true, rtl, ADD);
+            return getAdjustedCrop(crop, bitmapSize, displaySize, true, ADD);
         }
 
         // If any suggested crop is invalid, fallback to case 1
@@ -176,7 +176,7 @@
         // Case 2: if the orientation exists in the suggested crops, adjust the suggested crop
         Rect suggestedCrop = suggestedCrops.get(orientation);
         if (suggestedCrop != null) {
-                return getAdjustedCrop(suggestedCrop, bitmapSize, displaySize, true, rtl, ADD);
+            return getAdjustedCrop(suggestedCrop, bitmapSize, displaySize, true, ADD);
         }
 
         // Case 3: if we have the 90° rotated orientation in the suggested crops, reuse it and
@@ -188,7 +188,7 @@
         if (suggestedCrop != null) {
             // only keep the visible part (without parallax)
             Rect adjustedCrop = noParallax(suggestedCrop, suggestedDisplaySize, bitmapSize, rtl);
-            return getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, rtl, BALANCE);
+            return getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, BALANCE);
         }
 
         // Case 4: if the device is a foldable, if we're looking for a folded orientation and have
@@ -200,13 +200,13 @@
             // compute the visible part (without parallax) of the unfolded screen
             Rect adjustedCrop = noParallax(suggestedCrop, suggestedDisplaySize, bitmapSize, rtl);
             // compute the folded crop, at the center of the crop of the unfolded screen
-            Rect res = getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, rtl, REMOVE);
+            Rect res = getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, REMOVE);
             // if we removed some width, add it back to add a parallax effect
             if (res.width() < adjustedCrop.width()) {
                 if (rtl) res.left = Math.min(res.left, adjustedCrop.left);
                 else res.right = Math.max(res.right, adjustedCrop.right);
                 // use getAdjustedCrop(parallax=true) to make sure we don't exceed MAX_PARALLAX
-                res = getAdjustedCrop(res, bitmapSize, displaySize, true, rtl, ADD);
+                res = getAdjustedCrop(res, bitmapSize, displaySize, true, ADD);
             }
             return res;
         }
@@ -220,7 +220,7 @@
         if (suggestedCrop != null) {
             // only keep the visible part (without parallax)
             Rect adjustedCrop = noParallax(suggestedCrop, suggestedDisplaySize, bitmapSize, rtl);
-            return getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, rtl, ADD);
+            return getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, ADD);
         }
 
         // Case 6: for a foldable device, try to combine case 3 + case 4 or 5:
@@ -255,7 +255,7 @@
     @VisibleForTesting
     static Rect noParallax(Rect crop, Point displaySize, Point bitmapSize, boolean rtl) {
         if (displaySize == null) return crop;
-        Rect adjustedCrop = getAdjustedCrop(crop, bitmapSize, displaySize, true, rtl, ADD);
+        Rect adjustedCrop = getAdjustedCrop(crop, bitmapSize, displaySize, true, ADD);
         // only keep the visible part (without parallax)
         float suggestedDisplayRatio = 1f * displaySize.x / displaySize.y;
         int widthToRemove = (int) (adjustedCrop.width()
@@ -272,7 +272,7 @@
      * Adjust a given crop:
      * <ul>
      *     <li>If parallax = true, make sure we have a parallax of at most {@link #MAX_PARALLAX},
-     *     by removing content from the right (or left if RTL layout) if necessary.
+     *     by removing content from both sides if necessary.
      *     <li>If parallax = false, make sure we do not have additional width for parallax. If we
      *     have additional width for parallax, remove half of the additional width on both sides.
      *     <li>Make sure the crop fills the screen, i.e. that the width/height ratio of the crop
@@ -282,7 +282,7 @@
      */
     @VisibleForTesting
     static Rect getAdjustedCrop(Rect crop, Point bitmapSize, Point screenSize,
-            boolean parallax, boolean rtl, int mode) {
+            boolean parallax, int mode) {
         Rect adjustedCrop = new Rect(crop);
         float cropRatio = ((float) crop.width()) / crop.height();
         float screenRatio = ((float) screenSize.x) / screenSize.y;
@@ -297,8 +297,7 @@
                 Rect rotatedCrop = new Rect(newLeft, newTop, newRight, newBottom);
                 Point rotatedBitmap = new Point(bitmapSize.y, bitmapSize.x);
                 Point rotatedScreen = new Point(screenSize.y, screenSize.x);
-                Rect rect = getAdjustedCrop(rotatedCrop, rotatedBitmap, rotatedScreen, false, rtl,
-                        mode);
+                Rect rect = getAdjustedCrop(rotatedCrop, rotatedBitmap, rotatedScreen, false, mode);
                 int resultLeft = rect.top;
                 int resultRight = resultLeft + rect.height();
                 int resultTop = rotatedBitmap.x - rect.right;
@@ -318,9 +317,8 @@
             // total surface of W * H. In other words it is the width to add to get the desired
             // aspect ratio R, while preserving the total number of pixels W * H.
             int widthToAdd = mode == REMOVE ? 0
-                    : mode == ADD ? (int) (0.5 + crop.height() * screenRatio - crop.width())
-                    : (int) (0.5 - crop.width()
-                            + Math.sqrt(crop.width() * crop.height() * screenRatio));
+                    : mode == ADD ? (int) (crop.height() * screenRatio - crop.width())
+                    : (int) (-crop.width() + Math.sqrt(crop.width() * crop.height() * screenRatio));
             int availableWidth = bitmapSize.x - crop.width();
             if (availableWidth >= widthToAdd) {
                 int widthToAddLeft = widthToAdd / 2;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index d053bbb..2f6e07c 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -660,6 +660,8 @@
 
     private final TaskFragment.ConfigOverrideHint mResolveConfigHint;
 
+    private final boolean mOptOutEdgeToEdge;
+
     private static ConstrainDisplayApisConfig sConstrainDisplayApisConfig;
 
     boolean pendingVoiceInteractionStart;   // Waiting for activity-invoked voice session
@@ -2179,9 +2181,12 @@
                     || ent.array.getBoolean(R.styleable.Window_windowShowWallpaper, false);
             mStyleFillsParent = mOccludesParent;
             noDisplay = ent.array.getBoolean(R.styleable.Window_windowNoDisplay, false);
+            mOptOutEdgeToEdge = ent.array.getBoolean(
+                    R.styleable.Window_windowOptOutEdgeToEdgeEnforcement, false);
         } else {
             mStyleFillsParent = mOccludesParent = true;
             noDisplay = false;
+            mOptOutEdgeToEdge = false;
         }
 
         if (options != null) {
@@ -8710,9 +8715,9 @@
         if (rotation == ROTATION_UNDEFINED && !isFixedRotationTransforming()) {
             rotation = mDisplayContent.getRotation();
         }
-        if (!mResolveConfigHint.mUseOverrideInsetsForConfig
-                || getCompatDisplayInsets() != null || shouldCreateCompatDisplayInsets()
-                || isFloating(parentWindowingMode) || rotation == ROTATION_UNDEFINED) {
+        if (!mOptOutEdgeToEdge && (!mResolveConfigHint.mUseOverrideInsetsForConfig
+                || getCompatDisplayInsets() != null || isFloating(parentWindowingMode)
+                || rotation == ROTATION_UNDEFINED)) {
             // If the insets configuration decoupled logic is not enabled for the app, or the app
             // already has a compat override, or the context doesn't contain enough info to
             // calculate the override, skip the override.
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 62931bb..f7910b0 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -20,6 +20,7 @@
 import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
 import static android.app.ActivityOptions.BackgroundActivityStartMode;
 import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_COMPAT;
 import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
 import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -105,6 +106,7 @@
     static final String AUTO_OPT_IN_NOT_PENDING_INTENT = "notPendingIntent";
     static final String AUTO_OPT_IN_CALL_FOR_RESULT = "callForResult";
     static final String AUTO_OPT_IN_SAME_UID = "sameUid";
+    static final String AUTO_OPT_IN_COMPAT = "compatibility";
 
     /** If enabled the creator will not allow BAL on its behalf by default. */
     @ChangeId
@@ -303,6 +305,10 @@
             } else if (callingUid == realCallingUid && !balRequireOptInSameUid()) {
                 mAutoOptInReason = AUTO_OPT_IN_SAME_UID;
                 mAutoOptInCaller = false;
+            } else if (realCallerBackgroundActivityStartMode
+                    == MODE_BACKGROUND_ACTIVITY_START_COMPAT) {
+                mAutoOptInReason = AUTO_OPT_IN_COMPAT;
+                mAutoOptInCaller = false;
             } else {
                 mAutoOptInReason = null;
                 mAutoOptInCaller = false;
diff --git a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
index 42ca7b4..16fcb09 100644
--- a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
+++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
@@ -348,6 +348,9 @@
                         + bitmap.isMutable() + ") to (config=ARGB_8888, isMutable=false) failed.");
                 return false;
             }
+            final int width = bitmap.getWidth();
+            final int height = bitmap.getHeight();
+            bitmap.recycle();
 
             final File file = mPersistInfoProvider.getHighResolutionBitmapFile(mId, mUserId);
             try {
@@ -365,8 +368,8 @@
             }
 
             final Bitmap lowResBitmap = Bitmap.createScaledBitmap(swBitmap,
-                    (int) (bitmap.getWidth() * mPersistInfoProvider.lowResScaleFactor()),
-                    (int) (bitmap.getHeight() * mPersistInfoProvider.lowResScaleFactor()),
+                    (int) (width * mPersistInfoProvider.lowResScaleFactor()),
+                    (int) (height * mPersistInfoProvider.lowResScaleFactor()),
                     true /* filter */);
             swBitmap.recycle();
 
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index ce53290..2dc439d 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -492,6 +492,27 @@
         return false;
     }
 
+    /** Returns {@code true} if the display contains a transient-launch transition. */
+    boolean hasTransientLaunch(@NonNull DisplayContent dc) {
+        if (mCollectingTransition != null && mCollectingTransition.hasTransientLaunch()
+                && mCollectingTransition.isOnDisplay(dc)) {
+            return true;
+        }
+        for (int i = mWaitingTransitions.size() - 1; i >= 0; --i) {
+            final Transition transition = mWaitingTransitions.get(i);
+            if (transition.hasTransientLaunch() && transition.isOnDisplay(dc)) {
+                return true;
+            }
+        }
+        for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
+            final Transition transition = mPlayingTransitions.get(i);
+            if (transition.hasTransientLaunch() && transition.isOnDisplay(dc)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     boolean isTransientHide(@NonNull Task task) {
         if (mCollectingTransition != null && mCollectingTransition.isInTransientHide(task)) {
             return true;
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 65e1761..3e43f5a 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -165,7 +165,7 @@
                             || (w.mActivityRecord != null && !w.mActivityRecord.fillsParent());
                 }
             } else if (w.hasWallpaper() && mService.mPolicy.isKeyguardHostWindow(w.mAttrs)
-                    && w.mTransitionController.isTransitionOnDisplay(mDisplayContent)) {
+                    && w.mTransitionController.hasTransientLaunch(mDisplayContent)) {
                 // If we have no candidates at all, notification shade is allowed to be the target
                 // of last resort even if it has not been made visible yet.
                 if (DEBUG_WALLPAPER) Slog.v(TAG, "Found keyguard as wallpaper target: " + w);
diff --git a/services/core/jni/com_android_server_display_DisplayControl.cpp b/services/core/jni/com_android_server_display_DisplayControl.cpp
index 22c0f73..6613a25 100644
--- a/services/core/jni/com_android_server_display_DisplayControl.cpp
+++ b/services/core/jni/com_android_server_display_DisplayControl.cpp
@@ -23,20 +23,22 @@
 
 namespace android {
 
-static jobject nativeCreateDisplay(JNIEnv* env, jclass clazz, jstring nameObj, jboolean secure,
-                                   jstring uniqueIdStr, jfloat requestedRefreshRate) {
+static jobject nativeCreateVirtualDisplay(JNIEnv* env, jclass clazz, jstring nameObj,
+                                          jboolean secure, jstring uniqueIdStr,
+                                          jfloat requestedRefreshRate) {
     const ScopedUtfChars name(env, nameObj);
     const ScopedUtfChars uniqueId(env, uniqueIdStr);
-    sp<IBinder> token(SurfaceComposerClient::createDisplay(String8(name.c_str()), bool(secure),
-                                                           std::string(uniqueId.c_str()),
-                                                           requestedRefreshRate));
+    sp<IBinder> token(SurfaceComposerClient::createVirtualDisplay(std::string(name.c_str()),
+                                                                  bool(secure),
+                                                                  std::string(uniqueId.c_str()),
+                                                                  requestedRefreshRate));
     return javaObjectForIBinder(env, token);
 }
 
-static void nativeDestroyDisplay(JNIEnv* env, jclass clazz, jobject tokenObj) {
+static void nativeDestroyVirtualDisplay(JNIEnv* env, jclass clazz, jobject tokenObj) {
     sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
     if (token == NULL) return;
-    SurfaceComposerClient::destroyDisplay(token);
+    SurfaceComposerClient::destroyVirtualDisplay(token);
 }
 
 static void nativeOverrideHdrTypes(JNIEnv* env, jclass clazz, jobject tokenObject,
@@ -180,10 +182,10 @@
 
 static const JNINativeMethod sDisplayMethods[] = {
         // clang-format off
-    {"nativeCreateDisplay", "(Ljava/lang/String;ZLjava/lang/String;F)Landroid/os/IBinder;",
-            (void*)nativeCreateDisplay },
-    {"nativeDestroyDisplay", "(Landroid/os/IBinder;)V",
-            (void*)nativeDestroyDisplay },
+    {"nativeCreateVirtualDisplay", "(Ljava/lang/String;ZLjava/lang/String;F)Landroid/os/IBinder;",
+            (void*)nativeCreateVirtualDisplay },
+    {"nativeDestroyVirtualDisplay", "(Landroid/os/IBinder;)V",
+            (void*)nativeDestroyVirtualDisplay },
     {"nativeOverrideHdrTypes", "(Landroid/os/IBinder;[I)V",
                 (void*)nativeOverrideHdrTypes },
     {"nativeGetPhysicalDisplayIds", "()[J",
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 4c746a9..b19de18 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -601,7 +601,7 @@
     // Original data: [{'inputPort1': '1'}, {'inputPort2': '2'}]
     // Received data: ['inputPort1', '1', 'inputPort2', '2']
     // So we unpack accordingly here.
-    outConfig->portAssociations.clear();
+    outConfig->inputPortToDisplayPortAssociations.clear();
     jobjectArray portAssociations = jobjectArray(env->CallObjectMethod(mServiceObj,
             gServiceClassInfo.getInputPortAssociations));
     if (!checkAndClearExceptionFromCallback(env, "getInputPortAssociations") && portAssociations) {
@@ -618,16 +618,16 @@
                     displayPortStr.c_str());
                 continue;
             }
-            outConfig->portAssociations.insert({inputPort, displayPort});
+            outConfig->inputPortToDisplayPortAssociations.insert({inputPort, displayPort});
         }
         env->DeleteLocalRef(portAssociations);
     }
 
-    outConfig->uniqueIdAssociationsByPort = readMapFromInterleavedJavaArray<
+    outConfig->inputPortToDisplayUniqueIdAssociations = readMapFromInterleavedJavaArray<
             std::string>(gServiceClassInfo.getInputUniqueIdAssociationsByPort,
                          "getInputUniqueIdAssociationsByPort");
 
-    outConfig->uniqueIdAssociationsByDescriptor = readMapFromInterleavedJavaArray<
+    outConfig->inputDeviceDescriptorToDisplayUniqueIdAssociations = readMapFromInterleavedJavaArray<
             std::string>(gServiceClassInfo.getInputUniqueIdAssociationsByDescriptor,
                          "getInputUniqueIdAssociationsByDescriptor");
 
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index 3b25cb1..5f395c56 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -37,6 +37,7 @@
 import android.app.ActivityManagerInternal;
 import android.content.Context;
 import android.content.pm.PackageManagerInternal;
+import android.content.pm.UserInfo;
 import android.content.res.Configuration;
 import android.hardware.input.IInputManager;
 import android.hardware.input.InputManagerGlobal;
@@ -207,6 +208,16 @@
         when(mMockUserManagerInternal.getProfileIds(anyInt(), anyBoolean()))
                 .thenReturn(new int[] {0});
         when(mMockUserManagerInternal.getUserIds()).thenReturn(new int[] {0});
+        when(mMockUserManagerInternal.getUserInfo(anyInt())).thenAnswer(invocation -> {
+            final int userId = invocation.getArgument(0);
+            if (userId == 0) {
+                new UserInfo(userId, "main",
+                        UserInfo.FLAG_PRIMARY | UserInfo.FLAG_MAIN | UserInfo.FLAG_SYSTEM);
+            }
+            // TODO(b/315348827): Update mock for multi-user scenarios.
+            throw new UnsupportedOperationException(
+                    "Please mock #getUserInfo for userId=" + userId);
+        });
         when(mMockActivityManagerInternal.isSystemReady()).thenReturn(true);
         when(mMockActivityManagerInternal.getCurrentUserId()).thenReturn(mCallingUserId);
         when(mMockPackageManagerInternal.getPackageUid(anyString(), anyLong(), anyInt()))
diff --git a/services/tests/PackageManagerServiceTests/server/Android.bp b/services/tests/PackageManagerServiceTests/server/Android.bp
index ea7bb8b..a738acb 100644
--- a/services/tests/PackageManagerServiceTests/server/Android.bp
+++ b/services/tests/PackageManagerServiceTests/server/Android.bp
@@ -105,6 +105,7 @@
         ":PackageParserTestApp5",
         ":PackageParserTestApp6",
         ":PackageParserTestApp7",
+        ":PackageParserTestApp8",
     ],
     resource_zips: [":PackageManagerServiceServerTests_apks_as_resources"],
 
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
index a0e0e1e..5da202f 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
@@ -101,6 +101,7 @@
 import com.android.internal.pm.pkg.component.ParsedUsesPermission;
 import com.android.internal.pm.pkg.component.ParsedUsesPermissionImpl;
 import com.android.internal.pm.pkg.parsing.ParsingPackage;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.pm.parsing.PackageCacher;
 import com.android.server.pm.parsing.PackageInfoUtils;
@@ -126,6 +127,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -154,6 +156,7 @@
     private static final String TEST_APP5_APK = "PackageParserTestApp5.apk";
     private static final String TEST_APP6_APK = "PackageParserTestApp6.apk";
     private static final String TEST_APP7_APK = "PackageParserTestApp7.apk";
+    private static final String TEST_APP8_APK = "PackageParserTestApp8.apk";
     private static final String PACKAGE_NAME = "com.android.servicestests.apps.packageparserapp";
 
     @Before
@@ -814,6 +817,39 @@
         }
     }
 
+    @Test
+    @RequiresFlagsEnabled(android.content.res.Flags.FLAG_MANIFEST_FLAGGING)
+    public void testParseWithFeatureFlagAttributes() throws Exception {
+        final File testFile = extractFile(TEST_APP8_APK);
+        try (PackageParser2 parser = new TestPackageParser2()) {
+            Map<String, Boolean> flagValues = new HashMap<>();
+            flagValues.put("my.flag1", true);
+            flagValues.put("my.flag2", false);
+            flagValues.put("my.flag3", false);
+            flagValues.put("my.flag4", true);
+            ParsingPackageUtils.getAconfigFlags().addFlagValuesForTesting(flagValues);
+
+            // The manifest has:
+            //    <permission android:name="PERM1" android:featureFlag="my.flag1 " />
+            //    <permission android:name="PERM2" android:featureFlag=" !my.flag2" />
+            //    <permission android:name="PERM3" android:featureFlag="my.flag3" />
+            //    <permission android:name="PERM4" android:featureFlag="!my.flag4" />
+            //    <permission android:name="PERM5" android:featureFlag="unknown.flag" />
+            // Therefore with the above flag values, only PERM1 and PERM2 should be present.
+
+            final ParsedPackage pkg = parser.parsePackage(testFile, 0, false);
+            List<String> permissionNames =
+                    pkg.getPermissions().stream().map(ParsedComponent::getName).toList();
+            assertThat(permissionNames).contains(PACKAGE_NAME + ".PERM1");
+            assertThat(permissionNames).contains(PACKAGE_NAME + ".PERM2");
+            assertThat(permissionNames).doesNotContain(PACKAGE_NAME + ".PERM3");
+            assertThat(permissionNames).doesNotContain(PACKAGE_NAME + ".PERM4");
+            assertThat(permissionNames).doesNotContain(PACKAGE_NAME + ".PERM5");
+        } finally {
+            testFile.delete();
+        }
+    }
+
     /**
      * A subclass of package parser that adds a "cache_" prefix to the package name for the cached
      * results. This is used by tests to tell if a ParsedPackage is generated from the cache or not.
diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java
index 1322545..b98af6b 100644
--- a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java
@@ -37,6 +37,7 @@
 import android.service.dreams.Flags;
 import android.service.dreams.IDreamOverlayCallback;
 import android.testing.TestableLooper;
+import android.view.KeyEvent;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -181,4 +182,15 @@
         environment.advance(TestDreamEnvironment.DREAM_STATE_WOKEN);
         verify(environment.getDreamOverlayClient()).onWakeRequested();
     }
+
+    @Test
+    @EnableFlags(Flags.FLAG_DREAM_HANDLES_CONFIRM_KEYS)
+    public void testPartialKeyHandling() throws Exception {
+        TestDreamEnvironment environment = new TestDreamEnvironment.Builder(mTestableLooper)
+                .build();
+        environment.advance(TestDreamEnvironment.DREAM_STATE_STARTED);
+
+        // Ensure service does not crash from only receiving up event.
+        environment.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SPACE));
+    }
 }
diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java b/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java
index ef85ba5..3d03bf2 100644
--- a/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java
@@ -46,6 +46,7 @@
 import android.service.dreams.IDreamOverlayClient;
 import android.service.dreams.IDreamService;
 import android.testing.TestableLooper;
+import android.view.KeyEvent;
 import android.view.View;
 import android.view.Window;
 import android.view.WindowInsetsController;
@@ -390,6 +391,13 @@
         }
     }
 
+    /**
+     * Sends a key event to the dream.
+     */
+    public void dispatchKeyEvent(KeyEvent event) {
+        mService.dispatchKeyEvent(event);
+    }
+
     private void wakeDream() throws RemoteException {
         mService.wakeUp();
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index c359412..cb15d6f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -3094,13 +3094,14 @@
         ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
         verify(alarmPi).send(eq(mMockContext), eq(0), any(Intent.class),
                 any(), any(Handler.class), isNull(), bundleCaptor.capture());
+        Bundle bundle = bundleCaptor.getValue();
         if (idleOptions != null) {
-            assertEquals(idleOptions, bundleCaptor.getValue());
+            assertEquals(idleOptions, bundle);
         } else {
-            assertFalse("BAL flag needs to be false in alarm manager",
-                    bundleCaptor.getValue().getBoolean(
-                            ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED,
-                            true));
+            ActivityOptions options = ActivityOptions.fromBundle(bundle);
+            assertEquals("BAL should not be allowed in alarm manager",
+                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED,
+                    options.getPendingIntentBackgroundActivityStartMode());
         }
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java b/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
index 8e1e339..c77ab0f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
@@ -24,7 +24,6 @@
 import android.content.ContentResolver;
 import android.os.SystemProperties;
 import android.provider.Settings;
-import android.provider.DeviceConfig.Properties;
 import android.text.TextUtils;
 
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
@@ -43,7 +42,6 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
 
 /**
  * Test SettingsToPropertiesMapper.
@@ -63,7 +61,6 @@
 
     private HashMap<String, String> mSystemSettingsMap;
     private HashMap<String, String> mGlobalSettingsMap;
-    private HashMap<String, String> mConfigSettingsMap;
 
     @Before
     public void setUp() throws Exception {
@@ -74,11 +71,9 @@
                         .spyStatic(SystemProperties.class)
                         .spyStatic(Settings.Global.class)
                         .spyStatic(SettingsToPropertiesMapper.class)
-                        .spyStatic(Settings.Config.class)
                         .startMocking();
         mSystemSettingsMap = new HashMap<>();
         mGlobalSettingsMap = new HashMap<>();
-        mConfigSettingsMap = new HashMap<>();
 
         // Mock SystemProperties setter and various getters
         doAnswer((Answer<Void>) invocationOnMock -> {
@@ -106,21 +101,6 @@
                 }
         ).when(() -> Settings.Global.getString(any(), anyString()));
 
-        // Mock Settings.Config getstrings method
-        doAnswer((Answer<Map<String, String>>) invocationOnMock -> {
-                    String namespace = invocationOnMock.getArgument(0);
-                    List<String> flags = invocationOnMock.getArgument(1);
-                    HashMap<String, String> values = new HashMap<>();
-                    for (String flag : flags) {
-                      String value = mConfigSettingsMap.get(namespace + "/" + flag);
-                      if (value != null) {
-                        values.put(flag, value);
-                      }
-                    }
-                    return values;
-                }
-        ).when(() -> Settings.Config.getStrings(anyString(), any()));
-
         mTestMapper = new SettingsToPropertiesMapper(
             mMockContentResolver, TEST_MAPPING, new String[] {}, new String[] {});
     }
@@ -259,39 +239,4 @@
         Assert.assertTrue(categories.contains("category2"));
         Assert.assertTrue(categories.contains("category3"));
     }
-
-  @Test
-  public void testGetStagedFlagsWithValueChange() {
-    // mock up what is in the setting already
-    mConfigSettingsMap.put("namespace_1/flag_1", "true");
-    mConfigSettingsMap.put("namespace_1/flag_2", "true");
-
-    // mock up input
-    String namespace = "staged";
-    Map<String, String> keyValueMap = new HashMap<>();
-    // case 1: existing prop, stage the same value
-    keyValueMap.put("namespace_1*flag_1", "true");
-    // case 2: existing prop, stage a different value
-    keyValueMap.put("namespace_1*flag_2", "false");
-    // case 3: new prop
-    keyValueMap.put("namespace_2*flag_1", "true");
-    Properties props = new Properties(namespace, keyValueMap);
-
-    HashMap<String, HashMap<String, String>> toStageProps =
-        SettingsToPropertiesMapper.getStagedFlagsWithValueChange(props);
-
-    HashMap<String, String> namespace_1_to_stage = toStageProps.get("namespace_1");
-    HashMap<String, String> namespace_2_to_stage = toStageProps.get("namespace_2");
-    Assert.assertTrue(namespace_1_to_stage != null);
-    Assert.assertTrue(namespace_2_to_stage != null);
-
-    String namespace_1_flag_1 = namespace_1_to_stage.get("flag_1");
-    String namespace_1_flag_2 = namespace_1_to_stage.get("flag_2");
-    String namespace_2_flag_1 = namespace_2_to_stage.get("flag_1");
-    Assert.assertTrue(namespace_1_flag_1 == null);
-    Assert.assertTrue(namespace_1_flag_2 != null);
-    Assert.assertTrue(namespace_2_flag_1 != null);
-    Assert.assertTrue(namespace_1_flag_2.equals("false"));
-    Assert.assertTrue(namespace_2_flag_1.equals("true"));
-  }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
index 29f3720..1b0a8d2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
@@ -210,12 +210,10 @@
                     new Rect(0, 0, bitmapSize.x, bitmapSize.y),
                     new Rect(100, 200, bitmapSize.x - 100, bitmapSize.y))) {
                 for (int mode: ALL_MODES) {
-                    for (boolean rtl: List.of(true, false)) {
-                        for (boolean parallax: List.of(true, false)) {
-                            assertThat(WallpaperCropper.getAdjustedCrop(
-                                    crop, bitmapSize, displaySize, parallax, rtl, mode))
-                                    .isEqualTo(crop);
-                        }
+                    for (boolean parallax: List.of(true, false)) {
+                        assertThat(WallpaperCropper.getAdjustedCrop(
+                                crop, bitmapSize, displaySize, parallax, mode))
+                                .isEqualTo(crop);
                     }
                 }
             }
@@ -235,11 +233,9 @@
         int expectedWidth = (int) (displaySize.x * (1 + WallpaperCropper.MAX_PARALLAX));
         Point expectedCropSize = new Point(expectedWidth, 1000);
         for (int mode: ALL_MODES) {
-            for (boolean rtl: List.of(false, true)) {
-                assertThat(WallpaperCropper.getAdjustedCrop(
-                        crop, bitmapSize, displaySize, true, rtl, mode))
-                        .isEqualTo(centerOf(crop, expectedCropSize));
-            }
+            assertThat(WallpaperCropper.getAdjustedCrop(
+                    crop, bitmapSize, displaySize, true, mode))
+                    .isEqualTo(centerOf(crop, expectedCropSize));
         }
     }
 
@@ -258,11 +254,9 @@
             Point bitmapSize = new Point(acceptableWidth, 1000);
             Rect crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
             for (int mode : ALL_MODES) {
-                for (boolean rtl : List.of(false, true)) {
-                    assertThat(WallpaperCropper.getAdjustedCrop(
-                            crop, bitmapSize, displaySize, true, rtl, mode))
-                            .isEqualTo(crop);
-                }
+                assertThat(WallpaperCropper.getAdjustedCrop(
+                        crop, bitmapSize, displaySize, true, mode))
+                        .isEqualTo(crop);
             }
         }
     }
@@ -292,11 +286,9 @@
         for (int i = 0; i < crops.size(); i++) {
             Rect crop = crops.get(i);
             Rect expectedCrop = expectedAdjustedCrops.get(i);
-            for (boolean rtl: List.of(false, true)) {
-                assertThat(WallpaperCropper.getAdjustedCrop(
-                        crop, bitmapSize, displaySize, false, rtl, WallpaperCropper.ADD))
-                        .isEqualTo(expectedCrop);
-            }
+            assertThat(WallpaperCropper.getAdjustedCrop(
+                    crop, bitmapSize, displaySize, false, WallpaperCropper.ADD))
+                    .isEqualTo(expectedCrop);
         }
     }
 
@@ -317,11 +309,9 @@
         Point expectedCropSize = new Point(1000, 1000);
 
         for (Rect crop: crops) {
-            for (boolean rtl : List.of(false, true)) {
-                assertThat(WallpaperCropper.getAdjustedCrop(
-                        crop, bitmapSize, displaySize, false, rtl, WallpaperCropper.REMOVE))
-                        .isEqualTo(centerOf(crop, expectedCropSize));
-            }
+            assertThat(WallpaperCropper.getAdjustedCrop(
+                    crop, bitmapSize, displaySize, false, WallpaperCropper.REMOVE))
+                    .isEqualTo(centerOf(crop, expectedCropSize));
         }
     }
 
@@ -348,14 +338,14 @@
             Rect crop = crops.get(i);
             Rect expected = expectedAdjustedCrops.get(i);
             assertThat(WallpaperCropper.getAdjustedCrop(
-                    crop, bitmapSize, displaySize, false, false, WallpaperCropper.BALANCE))
+                    crop, bitmapSize, displaySize, false, WallpaperCropper.BALANCE))
                     .isEqualTo(expected);
 
             Rect transposedCrop = new Rect(crop.top, crop.left, crop.bottom, crop.right);
             Rect expectedTransposed = new Rect(
                     expected.top, expected.left, expected.bottom, expected.right);
             assertThat(WallpaperCropper.getAdjustedCrop(transposedCrop, bitmapSize,
-                    transposedDisplaySize, false, false, WallpaperCropper.BALANCE))
+                    transposedDisplaySize, false, WallpaperCropper.BALANCE))
                     .isEqualTo(expectedTransposed);
         }
     }
diff --git a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
index 9862663..1db97b9 100644
--- a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
@@ -186,7 +186,8 @@
                 new FileDescriptor(), /* screenshotFd= */ null,
                 BugreportParams.BUGREPORT_MODE_FULL,
                 /* flags= */ 0, new Listener(new CountDownLatch(1)),
-                /* isScreenshotRequested= */ false);
+                /* isScreenshotRequested= */ false,
+                /* skipUserConsentUnused = */ false);
 
         assertThat(mInjector.isBugreportStarted()).isTrue();
     }
@@ -202,7 +203,8 @@
                 new FileDescriptor(), /* screenshotFd= */ null,
                 BugreportParams.BUGREPORT_MODE_FULL,
                 /* flags= */ 0, new Listener(new CountDownLatch(1)),
-                /* isScreenshotRequested= */ false);
+                /* isScreenshotRequested= */ false,
+                /* skipUserConsentUnused = */ false);
 
         assertThat(mInjector.isBugreportStarted()).isTrue();
     }
@@ -216,7 +218,8 @@
                         new FileDescriptor(), /* screenshotFd= */ null,
                         BugreportParams.BUGREPORT_MODE_FULL,
                         /* flags= */ 0, new Listener(new CountDownLatch(1)),
-                        /* isScreenshotRequested= */ false));
+                        /* isScreenshotRequested= */ false,
+                        /* skipUserConsentUnused = */ false));
 
         assertThat(thrown.getMessage()).contains("not an admin user");
     }
@@ -232,7 +235,8 @@
                         new FileDescriptor(), /* screenshotFd= */ null,
                         BugreportParams.BUGREPORT_MODE_REMOTE,
                         /* flags= */ 0, new Listener(new CountDownLatch(1)),
-                        /* isScreenshotRequested= */ false));
+                        /* isScreenshotRequested= */ false,
+                        /* skipUserConsentUnused = */ false));
 
         assertThat(thrown.getMessage()).contains("not affiliated to the device owner");
     }
@@ -243,7 +247,7 @@
         Listener listener = new Listener(latch);
         mService.retrieveBugreport(Binder.getCallingUid(), mContext.getPackageName(),
                 mContext.getUserId(), new FileDescriptor(), mBugreportFile,
-                /* keepOnRetrieval= */ false, listener);
+                /* keepOnRetrieval= */ false, /* skipUserConsent = */ false, listener);
         assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue();
         assertThat(listener.getErrorCode()).isEqualTo(
                 BugreportCallback.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE);
diff --git a/services/tests/servicestests/test-apps/PackageParserApp/Android.bp b/services/tests/servicestests/test-apps/PackageParserApp/Android.bp
index 131b380..3def48a 100644
--- a/services/tests/servicestests/test-apps/PackageParserApp/Android.bp
+++ b/services/tests/servicestests/test-apps/PackageParserApp/Android.bp
@@ -116,3 +116,20 @@
     resource_dirs: ["res"],
     manifest: "AndroidManifestApp7.xml",
 }
+
+android_test_helper_app {
+    name: "PackageParserTestApp8",
+    sdk_version: "current",
+    srcs: ["**/*.java"],
+    dex_preopt: {
+        enabled: false,
+    },
+    optimize: {
+        enabled: false,
+    },
+    resource_dirs: ["res"],
+    aaptflags: [
+        "--feature-flags my.flag1,my.flag2,my.flag3,my.flag4,unknown.flag",
+    ],
+    manifest: "AndroidManifestApp8.xml",
+}
diff --git a/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp8.xml b/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp8.xml
new file mode 100644
index 0000000..d489c1b
--- /dev/null
+++ b/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp8.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.servicestests.apps.packageparserapp" >
+
+    <application>
+        <activity android:name=".TestActivity"
+                  android:exported="true" />
+    </application>
+
+    <permission android:name="PERM1" android:featureFlag="my.flag1 " />
+    <permission android:name="PERM2" android:featureFlag=" !my.flag2" />
+    <permission android:name="PERM3" android:featureFlag="my.flag3" />
+    <permission android:name="PERM4" android:featureFlag="!my.flag4" />
+    <permission android:name="PERM5" android:featureFlag="unknown.flag" />
+</manifest>
\ No newline at end of file
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
index 37e0818..5787780 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
@@ -24,6 +24,8 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
@@ -250,6 +252,7 @@
                 case ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN:
                 case ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN:
                 case ActivityOptions.KEY_TRANSIENT_LAUNCH:
+                case ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED:
                 case "android:activity.animationFinishedListener":
                     // KEY_ANIMATION_FINISHED_LISTENER
                 case "android:activity.animSpecs": // KEY_ANIM_SPECS
@@ -319,7 +322,7 @@
             Log.e("ActivityOptionsTests", "Unknown key " + key + " is found. "
                     + "Please review if the given bundle should be protected with permissions.");
         }
-        assertTrue(unknownKeys.isEmpty());
+        assertThat(unknownKeys).isEmpty();
     }
 
     public static class TrampolineActivity extends Activity {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 5b1a18d..9b48cb9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -314,6 +314,18 @@
         // Wallpaper is invisible because the lowest show-when-locked activity is opaque.
         assertNull(wallpaperController.getWallpaperTarget());
 
+        // Only transient-launch transition will make notification shade as last resort target.
+        // This verifies that regular transition won't choose invisible keyguard as the target.
+        final WindowState keyguard = createWindow(null /* parent */,
+                WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE, "keyguard");
+        keyguard.mAttrs.flags |= FLAG_SHOW_WALLPAPER;
+        registerTestTransitionPlayer();
+        final Transition transition = wallpaperWindow.mTransitionController.createTransition(
+                WindowManager.TRANSIT_CHANGE);
+        transition.collect(keyguard);
+        wallpaperController.adjustWallpaperWindows();
+        assertNull(wallpaperController.getWallpaperTarget());
+
         // A show-when-locked wallpaper is used for lockscreen. So the top wallpaper should
         // be the one that is not show-when-locked.
         final WindowState wallpaperWindow2 = createWallpaperWindow(mDisplayContent);
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
index b8d1800..3f2b13a 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
@@ -290,6 +290,16 @@
     return Visibility.fromAccess(this.access)
 }
 
+/** Return the [access] flags without the visibility */
+fun clearVisibility(access: Int): Int {
+    return access and (Opcodes.ACC_PUBLIC or Opcodes.ACC_PROTECTED or Opcodes.ACC_PRIVATE).inv()
+}
+
+/** Return the visibility part of the [access] flags */
+fun getVisibility(access: Int): Int {
+    return access and (Opcodes.ACC_PUBLIC or Opcodes.ACC_PROTECTED or Opcodes.ACC_PRIVATE)
+}
+
 
 /*
 
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
index 6643492..c99ff0e 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
@@ -195,6 +195,8 @@
                 return null
             }
 
+            var newAccess = access
+
             // Maybe rename the method.
             val newName: String
             val renameTo = filter.getRenameTo(currentClassName, name, descriptor)
@@ -205,8 +207,9 @@
                 // (the one with the @substitute/replace annotation).
                 // `name` is the name of the method we're currently visiting, so it's usually a
                 // "...$ravewnwood" name.
-                if (!checkSubstitutionMethodCompatibility(
-                        classes, currentClassName, newName, name, descriptor, options.errors)) {
+                newAccess = checkSubstitutionMethodCompatibility(
+                        classes, currentClassName, newName, name, descriptor, options.errors)
+                if (newAccess == NOT_COMPATIBLE) {
                     return null
                 }
 
@@ -221,7 +224,7 @@
             // But note, we only use it when calling the super's method,
             // but not for visitMethodInner(), because when subclass wants to change access,
             // it can do so inside visitMethodInner().
-            val newAccess = updateAccessFlags(access, name, descriptor)
+            newAccess = updateAccessFlags(newAccess, name, descriptor)
 
             val ret = visitMethodInner(access, newName, descriptor, signature, exceptions, policy,
                 renameTo != null,
@@ -303,4 +306,4 @@
             return ret
         }
     }
-}
\ No newline at end of file
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/Helper.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/Helper.kt
index 9d66c32..dc4f26bd 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/Helper.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/Helper.kt
@@ -17,12 +17,19 @@
 
 import com.android.hoststubgen.HostStubGenErrors
 import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.asm.clearVisibility
 import com.android.hoststubgen.asm.getVisibility
 import com.android.hoststubgen.asm.isStatic
 
+const val NOT_COMPATIBLE: Int = -1
+
 /**
  * Make sure substitution from and to methods have matching definition.
- * (static-ness, visibility.)
+ * (static-ness, etc)
+ *
+ * If the methods are compatible, return the "merged" [access] of the new method.
+ *
+ * If they are not compatible, returns [NOT_COMPATIBLE]
  */
 fun checkSubstitutionMethodCompatibility(
     classes: ClassNodes,
@@ -31,33 +38,31 @@
     toMethodName: String, // the one with either a "_host" or "$ravenwood" prefix. (typically)
     descriptor: String,
     errors: HostStubGenErrors,
-): Boolean {
+): Int {
     val from = classes.findMethod(className, fromMethodName, descriptor)
     if (from == null) {
         errors.onErrorFound(
-            "Substitution-from method not found: $className.$fromMethodName$descriptor")
-        return false
+            "Substitution-from method not found: $className.$fromMethodName$descriptor"
+        )
+        return NOT_COMPATIBLE
     }
     val to = classes.findMethod(className, toMethodName, descriptor)
     if (to == null) {
         // This shouldn't happen, because the visitor visited this method...
         errors.onErrorFound(
-            "Substitution-to method not found: $className.$toMethodName$descriptor")
-        return false
+            "Substitution-to method not found: $className.$toMethodName$descriptor"
+        )
+        return NOT_COMPATIBLE
     }
 
     if (from.isStatic() != to.isStatic()) {
         errors.onErrorFound(
             "Substitution method must have matching static-ness: " +
-                    "$className.$fromMethodName$descriptor")
-        return false
-    }
-    if (from.getVisibility().ordinal > to.getVisibility().ordinal) {
-        errors.onErrorFound(
-            "Substitution method cannot have smaller visibility than original: " +
-                    "$className.$fromMethodName$descriptor")
-        return false
+                    "$className.$fromMethodName$descriptor"
+        )
+        return NOT_COMPATIBLE
     }
 
-    return true
+    // Return the substitution's access flag but with the original method's visibility.
+    return clearVisibility (to.access) or getVisibility(from.access)
 }
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
index 931f0c5..dd63892 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
@@ -644,9 +644,9 @@
           suffix="_host"
         )
 
-  public static int nativeAddThree_host(int);
+  private static int nativeAddThree_host(int);
     descriptor: (I)I
-    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
     Code:
       stack=2, locals=1, args_size=1
          x: iload_0
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations.java
index ab387e0..6d8a48a 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations.java
@@ -73,7 +73,8 @@
     @HostSideTestSubstitute(suffix = "_host")
     public static native int nativeAddThree(int value);
 
-    public static int nativeAddThree_host(int value) {
+    // This method is private, but at runtime, it'll inherit the visibility of the original method
+    private static int nativeAddThree_host(int value) {
         return value + 3;
     }
 
diff --git a/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/visitors/HelperTest.kt b/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/visitors/HelperTest.kt
index 0ea90ed..75e2536 100644
--- a/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/visitors/HelperTest.kt
+++ b/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/visitors/HelperTest.kt
@@ -71,7 +71,7 @@
             addClass(cn)
         }
 
-        fun check(from: MethodNode?, to: MethodNode?, expected: Boolean) {
+        fun check(from: MethodNode?, to: MethodNode?, expected: Int) {
             assertThat(checkSubstitutionMethodCompatibility(
                 classes,
                 cn.name,
@@ -82,21 +82,21 @@
             )).isEqualTo(expected)
         }
 
-        check(staticPublic, staticPublic, true)
-        check(staticPrivate, staticPrivate, true)
-        check(nonStaticPublic, nonStaticPublic, true)
-        check(nonStaticPProtected, nonStaticPProtected, true)
+        check(staticPublic, staticPublic, Opcodes.ACC_PUBLIC or Opcodes.ACC_STATIC)
+        check(staticPrivate, staticPrivate, Opcodes.ACC_PRIVATE or Opcodes.ACC_STATIC)
+        check(nonStaticPublic, nonStaticPublic, Opcodes.ACC_PUBLIC)
+        check(nonStaticPProtected, nonStaticPProtected, 0)
 
-        check(staticPublic, null, false)
-        check(null, staticPublic, false)
+        check(staticPublic, null, NOT_COMPATIBLE)
+        check(null, staticPublic, NOT_COMPATIBLE)
 
-        check(staticPublic, nonStaticPublic, false)
-        check(nonStaticPublic, staticPublic, false)
+        check(staticPublic, nonStaticPublic, NOT_COMPATIBLE)
+        check(nonStaticPublic, staticPublic, NOT_COMPATIBLE)
 
-        check(staticPublic, staticPrivate, false)
-        check(staticPrivate, staticPublic, true)
+        check(staticPublic, staticPrivate, Opcodes.ACC_PUBLIC or Opcodes.ACC_STATIC)
+        check(staticPrivate, staticPublic, Opcodes.ACC_PRIVATE or Opcodes.ACC_STATIC)
 
-        check(nonStaticPublic, nonStaticPProtected, false)
-        check(nonStaticPProtected, nonStaticPublic, true)
+        check(nonStaticPublic, nonStaticPProtected, Opcodes.ACC_PUBLIC)
+        check(nonStaticPProtected, nonStaticPublic, 0)
     }
 }
\ No newline at end of file