Merge "[API feedback] add an option to exclude parameters" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index 923b52d..e2a7cc8 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -27170,6 +27170,15 @@
 
 package android.media.quality {
 
+  @FlaggedApi("android.media.tv.flags.media_quality_fw") public final class ActiveProcessingPicture implements android.os.Parcelable {
+    ctor public ActiveProcessingPicture(int, @NonNull String);
+    method public int describeContents();
+    method public int getId();
+    method @NonNull public String getProfileId();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.quality.ActiveProcessingPicture> CREATOR;
+  }
+
   @FlaggedApi("android.media.tv.flags.media_quality_fw") public final class AmbientBacklightEvent implements android.os.Parcelable {
     ctor public AmbientBacklightEvent(int, @Nullable android.media.quality.AmbientBacklightMetadata);
     method public int describeContents();
@@ -27256,6 +27265,7 @@
   }
 
   @FlaggedApi("android.media.tv.flags.media_quality_fw") public final class MediaQualityManager {
+    method public void addActiveProcessingPictureListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.quality.MediaQualityManager.ActiveProcessingPictureListener);
     method public void createPictureProfile(@NonNull android.media.quality.PictureProfile);
     method public void createSoundProfile(@NonNull android.media.quality.SoundProfile);
     method @NonNull public java.util.List<android.media.quality.PictureProfile> getAvailablePictureProfiles(boolean);
@@ -27270,6 +27280,7 @@
     method public void registerAmbientBacklightCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.quality.MediaQualityManager.AmbientBacklightCallback);
     method public void registerPictureProfileCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.quality.MediaQualityManager.PictureProfileCallback);
     method public void registerSoundProfileCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.quality.MediaQualityManager.SoundProfileCallback);
+    method public void removeActiveProcessingPictureListener(@NonNull android.media.quality.MediaQualityManager.ActiveProcessingPictureListener);
     method public void removePictureProfile(@NonNull String);
     method public void removeSoundProfile(@NonNull String);
     method public void setAmbientBacklightEnabled(boolean);
@@ -27281,6 +27292,10 @@
     method public void updateSoundProfile(@NonNull String, @NonNull android.media.quality.SoundProfile);
   }
 
+  public static interface MediaQualityManager.ActiveProcessingPictureListener {
+    method public void onActiveProcessingPicturesChanged(@NonNull java.util.List<android.media.quality.ActiveProcessingPicture>);
+  }
+
   public abstract static class MediaQualityManager.AmbientBacklightCallback {
     ctor public MediaQualityManager.AmbientBacklightCallback();
     method public void onAmbientBacklightEvent(@NonNull android.media.quality.AmbientBacklightEvent);
@@ -41030,7 +41045,7 @@
     field public static final int TYPE_DATASET_SELECTED = 0; // 0x0
     field public static final int TYPE_SAVE_SHOWN = 3; // 0x3
     field public static final int TYPE_VIEW_REQUESTED_AUTOFILL = 6; // 0x6
-    field @FlaggedApi("android.service.autofill.autofill_w_metrics") public static final int UI_TYPE_CREDMAN = 4; // 0x4
+    field @FlaggedApi("android.service.autofill.autofill_w_metrics") public static final int UI_TYPE_CREDENTIAL_MANAGER = 4; // 0x4
     field public static final int UI_TYPE_DIALOG = 3; // 0x3
     field public static final int UI_TYPE_INLINE = 2; // 0x2
     field public static final int UI_TYPE_MENU = 1; // 0x1
@@ -55232,8 +55247,8 @@
     method public abstract void setTransformation(android.graphics.Matrix);
     method public abstract void setVisibility(int);
     method public abstract void setWebDomain(@Nullable String);
-    field @FlaggedApi("android.service.autofill.autofill_w_metrics") public static final String EXTRA_VIRTUAL_STRUCTURE_TYPE = "android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_TYPE";
-    field @FlaggedApi("android.service.autofill.autofill_w_metrics") public static final String EXTRA_VIRTUAL_STRUCTURE_VERSION_NUMBER = "android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER";
+    field @FlaggedApi("android.service.autofill.autofill_w_metrics") public static final String EXTRA_VIRTUAL_STRUCTURE_TYPE = "android.view.extra.VIRTUAL_STRUCTURE_TYPE";
+    field @FlaggedApi("android.service.autofill.autofill_w_metrics") public static final String EXTRA_VIRTUAL_STRUCTURE_VERSION_NUMBER = "android.view.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER";
   }
 
   public abstract static class ViewStructure.HtmlInfo {
diff --git a/core/api/lint-baseline.txt b/core/api/lint-baseline.txt
index ad5bd31..e71dffa 100644
--- a/core/api/lint-baseline.txt
+++ b/core/api/lint-baseline.txt
@@ -1,10 +1,4 @@
 // Baseline format: 1.0
-ActionValue: android.view.ViewStructure#EXTRA_VIRTUAL_STRUCTURE_TYPE:
-    Inconsistent extra value; expected `android.view.extra.VIRTUAL_STRUCTURE_TYPE`, was `android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_TYPE`
-ActionValue: android.view.ViewStructure#EXTRA_VIRTUAL_STRUCTURE_VERSION_NUMBER:
-    Inconsistent extra value; expected `android.view.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER`, was `android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER`
-
-
 BroadcastBehavior: android.app.AlarmManager#ACTION_NEXT_ALARM_CLOCK_CHANGED:
     Field 'ACTION_NEXT_ALARM_CLOCK_CHANGED' is missing @BroadcastBehavior
 BroadcastBehavior: android.app.AlarmManager#ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED:
@@ -1191,10 +1185,6 @@
     New API must be flagged with @FlaggedApi: field android.R.dimen.system_corner_radius_xlarge
 UnflaggedApi: android.R.dimen#system_corner_radius_xsmall:
     New API must be flagged with @FlaggedApi: field android.R.dimen.system_corner_radius_xsmall
-UnflaggedApi: android.R.integer#status_bar_notification_info_maxnum:
-    Changes from not deprecated to deprecated must be flagged with @FlaggedApi: field android.R.integer.status_bar_notification_info_maxnum
-UnflaggedApi: android.R.string#status_bar_notification_info_overflow:
-    Changes from not deprecated to deprecated must be flagged with @FlaggedApi: field android.R.string.status_bar_notification_info_overflow
 UnflaggedApi: android.accessibilityservice.AccessibilityService#OVERLAY_RESULT_INTERNAL_ERROR:
     New API must be flagged with @FlaggedApi: field android.accessibilityservice.AccessibilityService.OVERLAY_RESULT_INTERNAL_ERROR
 UnflaggedApi: android.accessibilityservice.AccessibilityService#OVERLAY_RESULT_INVALID:
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index c530a0d..4a4776d 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -7987,6 +7987,7 @@
 package android.media.quality {
 
   @FlaggedApi("android.media.tv.flags.media_quality_fw") public final class MediaQualityManager {
+    method public void addGlobalActiveProcessingPictureListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.quality.MediaQualityManager.ActiveProcessingPictureListener);
     method @NonNull public java.util.List<java.lang.String> getPictureProfileAllowList();
     method @NonNull public java.util.List<java.lang.String> getPictureProfilePackageNames();
     method @NonNull public java.util.List<android.media.quality.PictureProfile> getPictureProfilesByPackage(@NonNull String, boolean);
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index 9140bdf..349b4ed 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -1,10 +1,4 @@
 // Baseline format: 1.0
-ActionValue: android.view.contentcapture.ViewNode.ViewStructureImpl#EXTRA_VIRTUAL_STRUCTURE_TYPE:
-    Inconsistent extra value; expected `android.view.contentcapture.extra.VIRTUAL_STRUCTURE_TYPE`, was `android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_TYPE`
-ActionValue: android.view.contentcapture.ViewNode.ViewStructureImpl#EXTRA_VIRTUAL_STRUCTURE_VERSION_NUMBER:
-    Inconsistent extra value; expected `android.view.contentcapture.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER`, was `android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER`
-
-
 BannedThrow: android.os.vibrator.persistence.VibrationXmlSerializer#serialize(android.os.VibrationEffect, java.io.Writer):
     Methods must not throw unchecked exceptions
 
diff --git a/core/java/android/service/autofill/FillEventHistory.java b/core/java/android/service/autofill/FillEventHistory.java
index fba8e42..6d62a2c 100644
--- a/core/java/android/service/autofill/FillEventHistory.java
+++ b/core/java/android/service/autofill/FillEventHistory.java
@@ -341,7 +341,7 @@
 
         /** Credential Manager suggestions are shown instead of Autofill suggestion */
         @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
-        public static final int UI_TYPE_CREDMAN = 4;
+        public static final int UI_TYPE_CREDENTIAL_MANAGER = 4;
 
         /** @hide */
         @IntDef(prefix = { "UI_TYPE_" }, value = {
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index c9d560c..802bddd 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -1301,17 +1301,23 @@
     }
 
     /**
-     * Represents the {@link FrameRateCategory} for the Normal frame rate
+     * Normal category determines the framework's recommended normal frame rate.
+     * Opt for this normal rate unless a higher frame rate significantly enhances
+     * the user experience.
      *
-     * @see FrameRateCategory
+     * @see #getSuggestedFrameRate(int)
+     * @see #FRAME_RATE_CATEGORY_HIGH
      */
     @FlaggedApi(FLAG_ENABLE_GET_SUGGESTED_FRAME_RATE)
     public static final int FRAME_RATE_CATEGORY_NORMAL = 0;
 
     /**
-     * Represents the {@link FrameRateCategory} for the High frame rate
+     * High category determines the framework's recommended high frame rate.
+     * Opt for this high rate when a higher frame rate significantly enhances
+     * the user experience.
      *
-     * @see FrameRateCategory
+     * @see #getSuggestedFrameRate(int)
+     * @see #FRAME_RATE_CATEGORY_NORMAL
      */
     @FlaggedApi(FLAG_ENABLE_GET_SUGGESTED_FRAME_RATE)
     public static final int FRAME_RATE_CATEGORY_HIGH = 1;
@@ -1332,22 +1338,17 @@
      *
      * <p> For example, an animation that does not require fast render rates can use
      * the {@link #FRAME_RATE_CATEGORY_NORMAL} to get the suggested frame rate.
-     * The suggested frame rate then can be used in the
-     * {@link Surface.FrameRateParams.Builder#setDesiredRateRange} for desiredMinRate.
      *
      * <pre>{@code
      *  float desiredMinRate = display.getSuggestedFrameRate(FRAME_RATE_CATEGORY_NORMAL);
-     *  Surface.FrameRateParams params = new Surface.FrameRateParams.Builder().
-     *                                      setDesiredRateRange(desiredMinRate, Float.MAX).build();
-     *  surface.setFrameRate(params);
+     *  surface.setFrameRate(desiredMinRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
      * }</pre>
      * </p>
      *
      * @param category either {@link #FRAME_RATE_CATEGORY_NORMAL}
      *                 or {@link #FRAME_RATE_CATEGORY_HIGH}
      *
-     * @see Surface#setFrameRate(Surface.FrameRateParams)
-     * @see SurfaceControl.Transaction#setFrameRate(SurfaceControl, Surface.FrameRateParams)
+     * @see Surface#setFrameRate(float, int)
      * @throws IllegalArgumentException when category is not {@link #FRAME_RATE_CATEGORY_NORMAL}
      * or {@link #FRAME_RATE_CATEGORY_HIGH}
      */
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 63bf392..9e97a8e 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -591,7 +591,7 @@
                 res.getBoolean(
                         com.android.internal.R.bool.config_viewBasedRotaryEncoderHapticsEnabled);
         mViewTouchScreenHapticScrollFeedbackEnabled =
-                Flags.enableTouchScrollFeedback()
+                Flags.enableScrollFeedbackForTouch()
                         ? res.getBoolean(
                         com.android.internal.R.bool
                                 .config_viewTouchScreenHapticScrollFeedbackEnabled)
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index 1be7f48..43a946a 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -87,8 +87,12 @@
      * <p>This value is added to mainly help with debugging purpose.
      */
     @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
+    @SuppressWarnings(
+            "ActionValue") // Lint expects this as
+                           // android.view.contentcapture.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER
+                           // but should not have contentcapture
     public static final String EXTRA_VIRTUAL_STRUCTURE_TYPE =
-            "android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_TYPE";
+            "android.view.extra.VIRTUAL_STRUCTURE_TYPE";
 
     /**
      * Key used for specifying the version of the view that generated the virtual structure for
@@ -98,8 +102,12 @@
      * "104.0.5112.69", then the value should be "104.0.5112.69"
      */
     @FlaggedApi(FLAG_AUTOFILL_W_METRICS)
+    @SuppressWarnings(
+            "ActionValue") // Lint expects this as
+                           // android.view.contentcapture.extra.VIRTUAL_STRUCTURE_TYPE
+                           // but should not have contentcapture
     public static final String EXTRA_VIRTUAL_STRUCTURE_VERSION_NUMBER =
-            "android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER";
+            "android.view.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER";
 
     /**
      * Set the identifier for this view.
diff --git a/core/java/android/view/flags/scroll_feedback_flags.aconfig b/core/java/android/view/flags/scroll_feedback_flags.aconfig
index ebda4d4..ddf6ff1 100644
--- a/core/java/android/view/flags/scroll_feedback_flags.aconfig
+++ b/core/java/android/view/flags/scroll_feedback_flags.aconfig
@@ -17,10 +17,10 @@
 }
 
 flag {
-    namespace: "toolkit"
-    name: "enable_touch_scroll_feedback"
+    namespace: "wear_frameworks"
+    name: "enable_scroll_feedback_for_touch"
     description: "Enables touchscreen haptic scroll feedback"
-    bug: "331830899"
+    bug: "382135785"
     is_fixed_read_only: true
 }
 
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 3c854ea..0721fd3 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -16,7 +16,7 @@
 
 package android.widget;
 
-import static android.view.flags.Flags.enableTouchScrollFeedback;
+import static android.view.flags.Flags.enableScrollFeedbackForTouch;
 import static android.view.flags.Flags.scrollFeedbackApi;
 import static android.view.flags.Flags.viewVelocityApi;
 
@@ -3737,7 +3737,7 @@
                     atEdge = trackMotionScroll(deltaY, incrementalDeltaY);
 
                     // TODO: b/360198915 - Add unit testing for using ScrollFeedbackProvider
-                    if (enableTouchScrollFeedback()) {
+                    if (enableScrollFeedbackForTouch()) {
                         initHapticScrollFeedbackProviderIfNotExists();
                         mHapticScrollFeedbackProvider.onScrollProgress(
                                 vtev.getDeviceId(), vtev.getSource(), MotionEvent.AXIS_Y,
@@ -3779,7 +3779,7 @@
                                     mTouchMode = TOUCH_MODE_OVERSCROLL;
                                 }
 
-                                if (enableTouchScrollFeedback()) {
+                                if (enableScrollFeedbackForTouch()) {
                                     initHapticScrollFeedbackProviderIfNotExists();
                                     mHapticScrollFeedbackProvider.onScrollLimit(
                                             vtev.getDeviceId(), vtev.getSource(),
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 511c832..184933f 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -16,7 +16,7 @@
 
 package android.widget;
 
-import static android.view.flags.Flags.enableTouchScrollFeedback;
+import static android.view.flags.Flags.enableScrollFeedbackForTouch;
 import static android.view.flags.Flags.viewVelocityApi;
 
 import android.annotation.ColorInt;
@@ -909,7 +909,7 @@
                 }
 
                 // TODO: b/360198915 - Add unit tests.
-                if (enableTouchScrollFeedback()) {
+                if (enableScrollFeedbackForTouch()) {
                     if (hitTopLimit || hitBottomLimit) {
                         initHapticScrollFeedbackProviderIfNotExists();
                         mHapticScrollFeedbackProvider.onScrollLimit(vtev.getDeviceId(),
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 8ebc1ed..a04071a 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -449,6 +449,14 @@
 }
 
 flag {
+    name: "reparent_window_token_api"
+    namespace: "lse_desktop_experience"
+    description: "Allows to reparent a window token to a different display"
+    is_fixed_read_only: true
+    bug: "381258683"
+}
+
+flag {
     name: "enable_desktop_windowing_hsum"
     namespace: "lse_desktop_experience"
     description: "Enables HSUM on desktop mode."
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index 9ea2943..f0613ce 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -398,27 +398,23 @@
                 new TaskFragmentAnimationParams.Builder();
         final int animationBackgroundColor = getAnimationBackgroundColor(splitAttributes);
         builder.setAnimationBackgroundColor(animationBackgroundColor);
-        if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
-            final int openAnimationResId =
-                    splitAttributes.getAnimationParams().getOpenAnimationResId();
-            builder.setOpenAnimationResId(openAnimationResId);
-            final int closeAnimationResId =
-                    splitAttributes.getAnimationParams().getCloseAnimationResId();
-            builder.setCloseAnimationResId(closeAnimationResId);
-            final int changeAnimationResId =
-                    splitAttributes.getAnimationParams().getChangeAnimationResId();
-            builder.setChangeAnimationResId(changeAnimationResId);
-        }
+        final int openAnimationResId =
+                splitAttributes.getAnimationParams().getOpenAnimationResId();
+        builder.setOpenAnimationResId(openAnimationResId);
+        final int closeAnimationResId =
+                splitAttributes.getAnimationParams().getCloseAnimationResId();
+        builder.setCloseAnimationResId(closeAnimationResId);
+        final int changeAnimationResId =
+                splitAttributes.getAnimationParams().getChangeAnimationResId();
+        builder.setChangeAnimationResId(changeAnimationResId);
         return builder.build();
     }
 
     @ColorInt
     private static int getAnimationBackgroundColor(@NonNull SplitAttributes splitAttributes) {
         int animationBackgroundColor = DEFAULT_ANIMATION_BACKGROUND_COLOR;
-        AnimationBackground animationBackground = splitAttributes.getAnimationBackground();
-        if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
-            animationBackground = splitAttributes.getAnimationParams().getAnimationBackground();
-        }
+        final AnimationBackground animationBackground =
+            splitAttributes.getAnimationParams().getAnimationBackground();
         if (animationBackground instanceof AnimationBackground.ColorBackground colorBackground) {
             animationBackgroundColor = colorBackground.getColor();
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index d2cef4b..f269b38 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -268,10 +268,7 @@
             final Animation animation =
                     animationProvider.get(info, change, openingWholeScreenBounds);
             if (shouldUseJumpCutForAnimation(animation)) {
-                if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
-                    return new ArrayList<>();
-                }
-                continue;
+                return new ArrayList<>();
             }
             final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter(
                     info, change, animation, openingWholeScreenBounds);
@@ -296,10 +293,7 @@
             final Animation animation =
                     animationProvider.get(info, change, closingWholeScreenBounds);
             if (shouldUseJumpCutForAnimation(animation)) {
-                if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
-                    return new ArrayList<>();
-                }
-                continue;
+                return new ArrayList<>();
             }
             final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter(
                     info, change, animation, closingWholeScreenBounds);
@@ -455,11 +449,9 @@
             final Animation[] animations =
                     mAnimationSpec.createChangeBoundsChangeAnimations(info, change, parentBounds);
             // Jump cut if either animation has zero for duration.
-            if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
-                for (Animation animation : animations) {
-                    if (shouldUseJumpCutForAnimation(animation)) {
-                        return new ArrayList<>();
-                    }
+            for (Animation animation : animations) {
+                if (shouldUseJumpCutForAnimation(animation)) {
+                    return new ArrayList<>();
                 }
             }
             // Keep track as we might need to add background color for the animation.
@@ -516,10 +508,8 @@
                         mAnimationSpec.createChangeBoundsOpenAnimation(info, change, parentBounds);
                 shouldShowBackgroundColor = false;
             }
-            if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
-                if (shouldUseJumpCutForAnimation(animation)) {
-                    return new ArrayList<>();
-                }
+            if (shouldUseJumpCutForAnimation(animation)) {
+                return new ArrayList<>();
             }
             adapters.add(new ActivityEmbeddingAnimationAdapter(animation, change,
                     TransitionUtil.getRootFor(change, info)));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
index 3046307..77799e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -96,11 +96,9 @@
     @NonNull
     Animation createChangeBoundsOpenAnimation(@NonNull TransitionInfo info,
             @NonNull TransitionInfo.Change change, @NonNull Rect parentBounds) {
-        if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
-            final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE);
-            if (customAnimation != null) {
-                return customAnimation;
-            }
+        final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE);
+        if (customAnimation != null) {
+            return customAnimation;
         }
         // Use end bounds for opening.
         final Rect bounds = change.getEndAbsBounds();
@@ -130,11 +128,9 @@
     @NonNull
     Animation createChangeBoundsCloseAnimation(@NonNull TransitionInfo info,
             @NonNull TransitionInfo.Change change, @NonNull Rect parentBounds) {
-        if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
-            final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE);
-            if (customAnimation != null) {
-                return customAnimation;
-            }
+        final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE);
+        if (customAnimation != null) {
+            return customAnimation;
         }
         // Use start bounds for closing.
         final Rect bounds = change.getStartAbsBounds();
@@ -168,14 +164,12 @@
     @NonNull
     Animation[] createChangeBoundsChangeAnimations(@NonNull TransitionInfo info,
             @NonNull TransitionInfo.Change change, @NonNull Rect parentBounds) {
-        if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
-            // TODO(b/293658614): Support more complicated animations that may need more than a noop
-            // animation as the start leash.
-            final Animation noopAnimation = createNoopAnimation(change);
-            final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE);
-            if (customAnimation != null) {
-                return new Animation[]{noopAnimation, customAnimation};
-            }
+        // TODO(b/293658614): Support more complicated animations that may need more than a noop
+        // animation as the start leash.
+        final Animation noopAnimation = createNoopAnimation(change);
+        final Animation customAnimation = loadCustomAnimation(info, change, TRANSIT_CHANGE);
+        if (customAnimation != null) {
+            return new Animation[]{noopAnimation, customAnimation};
         }
         // Both start bounds and end bounds are in screen coordinates. We will post translate
         // to the local coordinates in ActivityEmbeddingAnimationAdapter#onAnimationUpdate
@@ -320,13 +314,9 @@
         }
 
         final Animation anim;
-        if (Flags.activityEmbeddingAnimationCustomizationFlag()) {
-            // TODO(b/293658614): Consider allowing custom animations from non-default packages.
-            // Enforce limiting to animations from the default "android" package for now.
-            anim = mTransitionAnimation.loadDefaultAnimationRes(resId);
-        } else {
-            anim = mTransitionAnimation.loadAnimationRes(options.getPackageName(), resId);
-        }
+        // TODO(b/293658614): Consider allowing custom animations from non-default packages.
+        // Enforce limiting to animations from the default "android" package for now.
+        anim = mTransitionAnimation.loadDefaultAnimationRes(resId);
         if (anim != null) {
             return anim;
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index 80d8ecc..cd37113 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -53,6 +53,7 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.policy.SystemBarUtils;
+import com.android.window.flags.Flags;
 import com.android.wm.shell.R;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.common.DisplayController;
@@ -245,9 +246,17 @@
         final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
         final Resources resources = mContext.getResources();
         final DisplayMetrics metrics = resources.getDisplayMetrics();
-        final int screenWidth = metrics.widthPixels;
-        final int screenHeight = metrics.heightPixels;
-
+        final int screenWidth;
+        final int screenHeight;
+        if (Flags.enableBugFixesForSecondaryDisplay()) {
+            final DisplayLayout displayLayout =
+                    mDisplayController.getDisplayLayout(mTaskInfo.displayId);
+            screenWidth = displayLayout.width();
+            screenHeight = displayLayout.height();
+        } else {
+            screenWidth = metrics.widthPixels;
+            screenHeight = metrics.heightPixels;
+        }
         mView = new View(mContext);
         final SurfaceControl.Builder builder = new SurfaceControl.Builder();
         mRootTdaOrganizer.attachToDisplayArea(mTaskInfo.displayId, builder);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index d5a2a40..1ec8684 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -1202,9 +1202,12 @@
         moveHomeTask(wct, toTop = true)
 
         // Currently, we only handle the desktop on the default display really.
-        if (displayId == DEFAULT_DISPLAY && ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()) {
+        if (
+            (displayId == DEFAULT_DISPLAY || Flags.enableBugFixesForSecondaryDisplay()) &&
+                ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()
+        ) {
             // Add translucent wallpaper activity to show the wallpaper underneath
-            addWallpaperActivity(wct)
+            addWallpaperActivity(displayId, wct)
         }
 
         val expandedTasksOrderedFrontToBack = taskRepository.getExpandedTasksOrdered(displayId)
@@ -1253,7 +1256,7 @@
             ?.let { homeTask -> wct.reorder(homeTask.getToken(), /* onTop= */ toTop) }
     }
 
-    private fun addWallpaperActivity(wct: WindowContainerTransaction) {
+    private fun addWallpaperActivity(displayId: Int, wct: WindowContainerTransaction) {
         logV("addWallpaperActivity")
         val userHandle = UserHandle.of(userId)
         val userContext = context.createContextAsUser(userHandle, /* flags= */ 0)
@@ -1264,6 +1267,9 @@
                 launchWindowingMode = WINDOWING_MODE_FULLSCREEN
                 pendingIntentBackgroundActivityStartMode =
                     ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
+                if (Flags.enableBugFixesForSecondaryDisplay()) {
+                    launchDisplayId = displayId
+                }
             }
         val pendingIntent =
             PendingIntent.getActivityAsUser(
@@ -2048,7 +2054,11 @@
                     syncQueue,
                     taskInfo,
                     displayController,
-                    context,
+                    if (Flags.enableBugFixesForSecondaryDisplay()) {
+                        displayController.getDisplayContext(taskInfo.displayId)
+                    } else {
+                        context
+                    },
                     taskSurface,
                     rootTaskDisplayAreaOrganizer,
                     dragStartState,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 37d5878..032dac9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -1317,6 +1317,9 @@
                 // otherwise a new transition will notify the relevant observers
                 if (returningToApp && allAppsAreTranslucent(mPausingTasks)) {
                     mHomeTransitionObserver.notifyHomeVisibilityChanged(true);
+                } else if (!toHome && mState == STATE_NEW_TASK
+                        && allAppsAreTranslucent(mOpeningTasks)) {
+                    // We are opening a translucent app. Launcher is still visible so we do nothing.
                 } else if (!toHome) {
                     // For some transitions, we may have notified home activity that it became
                     // visible. We need to notify the observer that we are no longer going home.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 85e3068..0f5813c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -1634,7 +1634,9 @@
         }
         final DesktopModeWindowDecoration windowDecoration =
                 mDesktopModeWindowDecorFactory.create(
-                        mContext,
+                        Flags.enableBugFixesForSecondaryDisplay()
+                                ? mDisplayController.getDisplayContext(taskInfo.displayId)
+                                : mContext,
                         mContext.createContextAsUser(UserHandle.of(taskInfo.userId), 0 /* flags */),
                         mDisplayController,
                         mSplitScreenController,
diff --git a/media/java/android/media/quality/ActiveProcessingPicture.aidl b/media/java/android/media/quality/ActiveProcessingPicture.aidl
new file mode 100644
index 0000000..2851306
--- /dev/null
+++ b/media/java/android/media/quality/ActiveProcessingPicture.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.media.quality;
+
+parcelable ActiveProcessingPicture;
\ No newline at end of file
diff --git a/media/java/android/media/quality/ActiveProcessingPicture.java b/media/java/android/media/quality/ActiveProcessingPicture.java
new file mode 100644
index 0000000..e16ad62
--- /dev/null
+++ b/media/java/android/media/quality/ActiveProcessingPicture.java
@@ -0,0 +1,86 @@
+/*
+ * 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 android.media.quality;
+
+import android.annotation.FlaggedApi;
+import android.media.tv.flags.Flags;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Active picture represents an image or video undergoing picture processing which uses a picture
+ * profile. The picture profile is used to configure the picture processing parameters.
+ */
+@FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW)
+public final class ActiveProcessingPicture implements Parcelable {
+    private final int mId;
+    private final String mProfileId;
+
+    public ActiveProcessingPicture(int id, @NonNull String profileId) {
+        mId = id;
+        mProfileId = profileId;
+    }
+
+    /** @hide */
+    ActiveProcessingPicture(Parcel in) {
+        mId = in.readInt();
+        mProfileId = in.readString();
+    }
+
+    @NonNull
+    public static final Creator<ActiveProcessingPicture> CREATOR = new Creator<>() {
+        @Override
+        public ActiveProcessingPicture createFromParcel(Parcel in) {
+            return new ActiveProcessingPicture(in);
+        }
+
+        @Override
+        public ActiveProcessingPicture[] newArray(int size) {
+            return new ActiveProcessingPicture[size];
+        }
+    };
+
+    /**
+     * An ID that uniquely identifies the active content.
+     *
+     * <p>The ID is assigned by the system to distinguish different active contents.
+     */
+    public int getId() {
+        return mId;
+    }
+
+    /**
+     * The ID of the picture profile used to configure the content.
+     */
+    @NonNull
+    public String getProfileId() {
+        return mProfileId;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mId);
+        dest.writeString(mProfileId);
+    }
+}
diff --git a/media/java/android/media/quality/MediaQualityManager.java b/media/java/android/media/quality/MediaQualityManager.java
index 58cb99c..7e87462 100644
--- a/media/java/android/media/quality/MediaQualityManager.java
+++ b/media/java/android/media/quality/MediaQualityManager.java
@@ -20,6 +20,7 @@
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.content.Context;
@@ -56,6 +57,9 @@
     private final List<SoundProfileCallbackRecord> mSpCallbackRecords = new ArrayList<>();
     // @GuardedBy("mLock")
     private final List<AmbientBacklightCallbackRecord> mAbCallbackRecords = new ArrayList<>();
+    // @GuardedBy("mLock")
+    private final List<ActiveProcessingPictureListenerRecord> mApListenerRecords =
+            new ArrayList<>();
 
 
     /**
@@ -1058,4 +1062,86 @@
         public void onAmbientBacklightEvent(@NonNull AmbientBacklightEvent event) {
         }
     }
+
+    /**
+     * Listener used to monitor status of active pictures.
+     */
+    public interface ActiveProcessingPictureListener {
+        /**
+         * Called when active pictures are changed.
+         *
+         * @param activeProcessingPictures contents currently undergoing picture processing.
+         */
+        void onActiveProcessingPicturesChanged(
+                @NonNull List<ActiveProcessingPicture> activeProcessingPictures);
+    }
+
+    /**
+     * Adds an active picture listener for the contents owner by the caller.
+     */
+    public void addActiveProcessingPictureListener(
+            @CallbackExecutor @NonNull Executor executor,
+            @NonNull ActiveProcessingPictureListener listener) {
+        Preconditions.checkNotNull(listener);
+        Preconditions.checkNotNull(executor);
+        synchronized (mLock) {
+            mApListenerRecords.add(
+                    new ActiveProcessingPictureListenerRecord(listener, executor, false));
+        }
+    }
+
+    /**
+     * Adds an active picture listener for all contents.
+     *
+     * @hide
+     */
+    @SuppressLint("PairedRegistration")
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
+    public void addGlobalActiveProcessingPictureListener(
+            @NonNull Executor executor,
+            @NonNull ActiveProcessingPictureListener listener) {
+        Preconditions.checkNotNull(listener);
+        Preconditions.checkNotNull(executor);
+        synchronized (mLock) {
+            mApListenerRecords.add(
+                    new ActiveProcessingPictureListenerRecord(listener, executor, true));
+        }
+    }
+
+
+    /**
+     * Removes an active picture listener for the contents.
+     */
+    public void removeActiveProcessingPictureListener(
+            @NonNull ActiveProcessingPictureListener listener) {
+        Preconditions.checkNotNull(listener);
+        synchronized (mLock) {
+            for (Iterator<ActiveProcessingPictureListenerRecord> it = mApListenerRecords.iterator();
+                    it.hasNext(); ) {
+                ActiveProcessingPictureListenerRecord record = it.next();
+                if (record.getListener() == listener) {
+                    it.remove();
+                    break;
+                }
+            }
+        }
+    }
+
+    private static final class ActiveProcessingPictureListenerRecord {
+        private final ActiveProcessingPictureListener mListener;
+        private final Executor mExecutor;
+        private final boolean mIsGlobal;
+
+        ActiveProcessingPictureListenerRecord(
+                ActiveProcessingPictureListener listener, Executor executor, boolean isGlobal) {
+            mListener = listener;
+            mExecutor = executor;
+            mIsGlobal = isGlobal;
+        }
+
+        public ActiveProcessingPictureListener getListener() {
+            return mListener;
+        }
+    }
 }
diff --git a/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/BundleUtil.java b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/BundleUtil.java
index 53ef9e8..2626cc8 100644
--- a/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/BundleUtil.java
+++ b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/BundleUtil.java
@@ -21,6 +21,7 @@
 import static android.system.OsConstants.O_RDONLY;
 import static android.system.OsConstants.PROT_READ;
 
+import android.annotation.SuppressLint;
 import android.app.ondeviceintelligence.IResponseCallback;
 import android.app.ondeviceintelligence.IStreamingResponseCallback;
 import android.app.ondeviceintelligence.ITokenInfoCallback;
@@ -328,6 +329,7 @@
                 (value instanceof Boolean) || (value instanceof boolean[]);
     }
 
+    @SuppressLint("NewApi")
     private static void ensureValidBundle(Bundle bundle) {
         if (bundle == null) {
             throw new IllegalArgumentException("Request passed is expected to be non-null");
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index c1f7868..ee22915 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1230,13 +1230,6 @@
 }
 
 flag {
-  name: "communal_hub_on_mobile"
-  namespace: "systemui"
-  description: "Brings the glanceable hub experience to mobile phones"
-  bug: "375689917"
-}
-
-flag {
   name: "glanceable_hub_v2"
   namespace: "systemui"
   description: "Gates the refreshed glanceable hub experience that also brings the glanceable hub to mobile phones"
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/ShadeDisplayAwareDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/ShadeDisplayAwareDetector.kt
index 2f83d82..92b6fd4 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/ShadeDisplayAwareDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/ShadeDisplayAwareDetector.kt
@@ -25,6 +25,7 @@
 import com.android.tools.lint.detector.api.Scope
 import com.android.tools.lint.detector.api.Severity
 import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiParameter
 import org.jetbrains.uast.UClass
 import org.jetbrains.uast.getContainingUFile
 
@@ -40,14 +41,8 @@
                     if (!isInRelevantShadePackage(node)) continue
                     if (IGNORED_PACKAGES.contains(node.qualifiedName)) continue
 
-                    // Check the any context-dependent parameter to see if it has @ShadeDisplayAware
-                    // annotation
                     for (parameter in constructor.parameterList.parameters) {
-                        val shouldReport =
-                            CONTEXT_DEPENDENT_SHADE_CLASSES.contains(
-                                parameter.type.canonicalText
-                            ) && !parameter.hasAnnotation(SHADE_DISPLAY_AWARE_ANNOTATION)
-                        if (shouldReport) {
+                        if (parameter.shouldReport()) {
                             context.report(
                                 issue = ISSUE,
                                 scope = parameter.declarationScope,
@@ -62,20 +57,35 @@
 
     companion object {
         private const val INJECT_ANNOTATION = "javax.inject.Inject"
+        private const val APPLICATION_ANNOTATION =
+            "com.android.systemui.dagger.qualifiers.Application"
+        private const val GLOBAL_CONFIG_ANNOTATION = "com.android.systemui.common.ui.GlobalConfig"
         private const val SHADE_DISPLAY_AWARE_ANNOTATION =
             "com.android.systemui.shade.ShadeDisplayAware"
 
+        private const val CONTEXT = "android.content.Context"
+        private const val WINDOW_MANAGER = "android.view.WindowManager"
+        private const val LAYOUT_INFLATER = "android.view.LayoutInflater"
+        private const val RESOURCES = "android.content.res.Resources"
+        private const val CONFIG_STATE = "com.android.systemui.common.ui.ConfigurationState"
+        private const val CONFIG_CONTROLLER =
+            "com.android.systemui.statusbar.policy.ConfigurationController"
+        private const val CONFIG_INTERACTOR =
+            "com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor"
+
         private val CONTEXT_DEPENDENT_SHADE_CLASSES =
             setOf(
-                "android.content.Context",
-                "android.view.WindowManager",
-                "android.view.LayoutInflater",
-                "android.content.res.Resources",
-                "com.android.systemui.common.ui.ConfigurationState",
-                "com.android.systemui.statusbar.policy.ConfigurationController",
-                "com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor",
+                CONTEXT,
+                WINDOW_MANAGER,
+                LAYOUT_INFLATER,
+                RESOURCES,
+                CONFIG_STATE,
+                CONFIG_CONTROLLER,
+                CONFIG_INTERACTOR,
             )
 
+        private val CONFIG_CLASSES = setOf(CONFIG_STATE, CONFIG_CONTROLLER, CONFIG_INTERACTOR)
+
         private val SHADE_WINDOW_PACKAGES =
             listOf(
                 "com.android.systemui.biometrics",
@@ -93,6 +103,22 @@
                 "com.android.systemui.qs.customize.TileAdapter",
             )
 
+        private fun PsiParameter.shouldReport(): Boolean {
+            val className = type.canonicalText
+
+            // check if the parameter is a context-dependent class relevant to shade
+            if (className !in CONTEXT_DEPENDENT_SHADE_CLASSES) return false
+            // check if it has @ShadeDisplayAware
+            if (hasAnnotation(SHADE_DISPLAY_AWARE_ANNOTATION)) return false
+            // check if its a @Application-annotated Context
+            if (className == CONTEXT && hasAnnotation(APPLICATION_ANNOTATION)) return false
+            // check if its a @GlobalConfig-annotated ConfigurationState, ConfigurationController
+            // or ConfigurationInteractor
+            if (className in CONFIG_CLASSES && hasAnnotation(GLOBAL_CONFIG_ANNOTATION)) return false
+
+            return true
+        }
+
         private fun isInRelevantShadePackage(node: UClass): Boolean {
             val packageName = node.getContainingUFile()?.packageName
             if (packageName.isNullOrBlank()) return false
@@ -102,15 +128,15 @@
         }
 
         private fun reportMsg(className: String) =
-            """UI elements of the shade window
-              |should use ShadeDisplayAware-annotated $className, as the shade might move between windows, and only
-              |@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
-              |might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
-              |If the usage of $className is not related to display specific configuration or UI, then there is
-              |technically no need to use the annotation, and you can annotate the class with
-              |@SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")
-              |"""
-                .trimMargin()
+            "UI elements of the shade window should use " +
+                "ShadeDisplayAware-annotated $className, as the shade might move between windows, " +
+                "and only @ShadeDisplayAware resources are updated with the new configuration " +
+                "correctly. Failures to do so might result in wrong dimensions for shade window " +
+                "classes (e.g. using the wrong density or theme). If the usage of $className is " +
+                "not related to display specific configuration or UI, then there is technically " +
+                "no need to use the annotation, and you can annotate the class with " +
+                "@SuppressLint(\"ShadeDisplayAwareContextChecker\")/" +
+                "@Suppress(\"ShadeDisplayAwareContextChecker\")".trimMargin()
 
         @JvmField
         val ISSUE: Issue =
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/ShadeDisplayAwareDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/ShadeDisplayAwareDetectorTest.kt
index 58ad363..79f1907 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/ShadeDisplayAwareDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/ShadeDisplayAwareDetectorTest.kt
@@ -63,6 +63,26 @@
             )
             .indented()
 
+    private val applicationStub: TestFile =
+        kotlin(
+                """
+                package com.android.systemui.dagger.qualifiers
+
+                @Retention(AnnotationRetention.RUNTIME) annotation class Application
+                """
+            )
+            .indented()
+
+    private val globalConfigStub: TestFile =
+        kotlin(
+                """
+                package com.android.systemui.common.ui
+
+                @Retention(AnnotationRetention.RUNTIME) annotation class GlobalConfig
+                """
+            )
+            .indented()
+
     private val configStateStub: TestFile =
         kotlin(
                 """
@@ -98,6 +118,8 @@
             injectStub,
             qsContext,
             shadeDisplayAwareStub,
+            applicationStub,
+            globalConfigStub,
             configStateStub,
             configControllerStub,
             configInteractorStub,
@@ -259,6 +281,60 @@
     }
 
     @Test
+    fun injectedConstructor_inRelevantPackage_withApplicationAnnotatedContext() {
+        lint()
+            .files(
+                TestFiles.kotlin(
+                    """
+                        package com.android.systemui.shade.example
+
+                        import javax.inject.Inject
+                        import android.content.Context
+                        import com.android.systemui.dagger.qualifiers.Application
+
+                        class ExampleClass
+                            @Inject
+                            constructor(@Application private val context: Context)
+                    """
+                        .trimIndent()
+                ),
+                *androidStubs,
+                *otherStubs,
+            )
+            .issues(ShadeDisplayAwareDetector.ISSUE)
+            .testModes(TestMode.DEFAULT)
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun injectedConstructor_inRelevantPackage_withGlobalConfigAnnotatedConfigurationClass() {
+        lint()
+            .files(
+                TestFiles.kotlin(
+                    """
+                        package com.android.systemui.shade.example
+
+                        import javax.inject.Inject
+                        import com.android.systemui.common.ui.ConfigurationState
+                        import com.android.systemui.common.ui.GlobalConfig
+
+                        class ExampleClass
+                            @Inject
+                            constructor(@GlobalConfig private val configState: ConfigurationState)
+                    """
+                        .trimIndent()
+                ),
+                *androidStubs,
+                *otherStubs,
+            )
+            .issues(ShadeDisplayAwareDetector.ISSUE)
+            .testModes(TestMode.DEFAULT)
+            .run()
+            .expectClean()
+    }
+
+    @Test
     fun injectedConstructor_notInRelevantPackage_withRelevantParameter_withoutAnnotation() {
         lint()
             .files(
@@ -363,13 +439,13 @@
     }
 
     private fun errorMsgString(lineNumber: Int, className: String) =
-        """
-        src/com/android/systemui/shade/example/ExampleClass.kt:$lineNumber: Error: UI elements of the shade window
-        should use ShadeDisplayAware-annotated $className, as the shade might move between windows, and only
-        @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so
-        might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).
-        If the usage of $className is not related to display specific configuration or UI, then there is
-        technically no need to use the annotation, and you can annotate the class with
-        @SuppressLint("ShadeDisplayAwareContextChecker")/@Suppress("ShadeDisplayAwareContextChecker")
-    """
+        "src/com/android/systemui/shade/example/ExampleClass.kt:$lineNumber: Error: UI elements of " +
+            "the shade window should use ShadeDisplayAware-annotated $className, as the shade " +
+            "might move between windows, and only @ShadeDisplayAware resources are updated with " +
+            "the new configuration correctly. Failures to do so might result in wrong dimensions " +
+            "for shade window classes (e.g. using the wrong density or theme). If the usage of " +
+            "$className is not related to display specific configuration or UI, then there is " +
+            "technically no need to use the annotation, and you can annotate the class with " +
+            "@SuppressLint(\"ShadeDisplayAwareContextChecker\")" +
+            "/@Suppress(\"ShadeDisplayAwareContextChecker\")"
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 4705d8d..beaf963 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -44,7 +44,6 @@
 import com.android.compose.animation.scene.Swipe
 import com.android.compose.animation.scene.observableTransitionState
 import com.android.compose.animation.scene.transitions
-import com.android.systemui.Flags.communalHubOnMobile
 import com.android.systemui.communal.shared.model.CommunalBackgroundType
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.shared.model.CommunalTransitionKeys
@@ -188,7 +187,7 @@
         scene(
             CommunalScenes.Blank,
             userActions =
-                if (communalHubOnMobile()) emptyMap()
+                if (viewModel.v2FlagEnabled()) emptyMap()
                 else mapOf(Swipe.Start(fromSource = Edge.End) to CommunalScenes.Communal),
         ) {
             // This scene shows nothing only allowing for transitions to the communal scene.
@@ -198,7 +197,8 @@
         scene(
             CommunalScenes.Communal,
             userActions =
-                if (communalHubOnMobile()) emptyMap() else mapOf(Swipe.End to CommunalScenes.Blank),
+                if (viewModel.v2FlagEnabled()) emptyMap()
+                else mapOf(Swipe.End to CommunalScenes.Blank),
         ) {
             CommunalScene(
                 backgroundType = backgroundType,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
index 778d7e7..3926b32 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
@@ -24,6 +24,7 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.unit.IntRect
+import androidx.compose.ui.zIndex
 import com.android.compose.animation.scene.SceneScope
 import com.android.systemui.communal.smartspace.SmartspaceInteractionHandler
 import com.android.systemui.communal.ui.compose.section.AmbientStatusBarSection
@@ -59,7 +60,7 @@
                     Box(modifier = Modifier.fillMaxSize()) {
                         with(communalPopupSection) { Popup() }
                         with(ambientStatusBarSection) {
-                            AmbientStatusBar(modifier = Modifier.fillMaxWidth())
+                            AmbientStatusBar(modifier = Modifier.fillMaxWidth().zIndex(1f))
                         }
                         CommunalHub(
                             viewModel = viewModel,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
index 2d32fd7..f7ce215 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
@@ -261,7 +261,7 @@
 
 /** A button with an icon. */
 @Composable
-private fun IconButton(model: FooterActionsButtonViewModel, modifier: Modifier = Modifier) {
+fun IconButton(model: FooterActionsButtonViewModel, modifier: Modifier = Modifier) {
     Expandable(
         color = colorAttr(model.backgroundColor),
         shape = CircleShape,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
index 3fce890..b1a1945 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
@@ -26,8 +26,10 @@
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.heightIn
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.requiredHeightIn
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
@@ -50,6 +52,8 @@
 import com.android.systemui.qs.panels.ui.compose.EditMode
 import com.android.systemui.qs.panels.ui.compose.TileDetails
 import com.android.systemui.qs.panels.ui.compose.TileGrid
+import com.android.systemui.qs.panels.ui.compose.toolbar.Toolbar
+import com.android.systemui.qs.ui.composable.QuickSettingsShade.Dimensions.GridMaxHeight
 import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel
 import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeOverlayActionsViewModel
 import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeOverlayContentViewModel
@@ -122,7 +126,9 @@
 // A sealed interface to represent the possible states of the `ShadeBody`
 sealed interface ShadeBodyState {
     data object Editing : ShadeBodyState
+
     data object TileDetails : ShadeBodyState
+
     data object Default : ShadeBodyState
 }
 
@@ -149,9 +155,8 @@
             ShadeBodyState.Editing -> {
                 EditMode(
                     viewModel = viewModel.editModeViewModel,
-                    modifier = Modifier
-                        .fillMaxWidth()
-                        .padding(QuickSettingsShade.Dimensions.Padding),
+                    modifier =
+                        Modifier.fillMaxWidth().padding(QuickSettingsShade.Dimensions.Padding),
                 )
             }
             ShadeBodyState.TileDetails -> {
@@ -182,22 +187,23 @@
                 .padding(
                     start = QuickSettingsShade.Dimensions.Padding,
                     end = QuickSettingsShade.Dimensions.Padding,
-                    top = QuickSettingsShade.Dimensions.Padding,
+                    bottom = QuickSettingsShade.Dimensions.Padding / 2,
                 ),
     ) {
+        Toolbar(viewModel.toolbarViewModelFactory)
         BrightnessSliderContainer(
             viewModel = viewModel.brightnessSliderViewModel,
             modifier =
                 Modifier.fillMaxWidth().height(QuickSettingsShade.Dimensions.BrightnessSliderHeight),
         )
-        Box {
+        Box(
+            modifier =
+                Modifier.requiredHeightIn(max = GridMaxHeight)
+                    .verticalNestedScrollToScene()
+                    .verticalScroll(rememberScrollState())
+        ) {
             GridAnchor()
-            TileGrid(
-                viewModel = viewModel.tileGridViewModel,
-                modifier =
-                    Modifier.fillMaxWidth()
-                        .heightIn(max = QuickSettingsShade.Dimensions.GridMaxHeight),
-            )
+            TileGrid(viewModel = viewModel.tileGridViewModel, modifier = Modifier.fillMaxWidth())
         }
     }
 }
@@ -207,6 +213,6 @@
     object Dimensions {
         val Padding = 16.dp
         val BrightnessSliderHeight = 64.dp
-        val GridMaxHeight = 800.dp
+        val GridMaxHeight = 420.dp
     }
 }
diff --git a/packages/SystemUI/lint-baseline.xml b/packages/SystemUI/lint-baseline.xml
index 00b0c44..43131b1 100644
--- a/packages/SystemUI/lint-baseline.xml
+++ b/packages/SystemUI/lint-baseline.xml
@@ -33644,7 +33644,7 @@
 
     <issue
         id="ShadeDisplayAwareContextChecker"
-        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        message="UI elements of the shade window should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Resources is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
         errorLine1="    @Main resources: Resources,"
         errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
@@ -33655,7 +33655,7 @@
 
     <issue
         id="ShadeDisplayAwareContextChecker"
-        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Context is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        message="UI elements of the shade window should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Context is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
         errorLine1="constructor(context: Context, val shadeViewController: ShadeViewController) {"
         errorLine2="            ~~~~~~~~~~~~~~~~">
         <location
@@ -33699,7 +33699,7 @@
 
     <issue
         id="ShadeDisplayAwareContextChecker"
-        message="UI elements of the shade window&#xA;should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only&#xA;@ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so&#xA;might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme).&#xA;If the usage of Resources is not related to display specific configuration or UI, then there is&#xA;technically no need to use the annotation, and you can annotate the class with&#xA;@SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        message="UI elements of the shade window should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Resources is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
         errorLine1="    @Main private val resources: Resources,"
         errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
@@ -33763,5 +33763,201 @@
             column="5"/>
     </issue>
 
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Context is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    public AuthController(Context context,"
+        errorLine2="                                  ~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java"
+            line="716"
+            column="35"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window should use ShadeDisplayAware-annotated WindowManager, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of WindowManager is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="            @NonNull WindowManager windowManager,"
+        errorLine2="                                   ~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java"
+            line="721"
+            column="36"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Context is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    private val sysuiContext: Context,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt"
+            line="72"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window should use ShadeDisplayAware-annotated ConfigurationController, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of ConfigurationController is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    private val configurationController: ConfigurationController,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt"
+            line="74"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Context is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="            Context context,"
+        errorLine2="                    ~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationBroadcastReceiver.java"
+            line="46"
+            column="21"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Resources is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="            @Main Resources resources,"
+        errorLine2="                            ~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationDialogFactory.java"
+            line="52"
+            column="29"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Context is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    public BiometricNotificationService(@NonNull Context context,"
+        errorLine2="                                                         ~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/BiometricNotificationService.java"
+            line="148"
+            column="58"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Context is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    c: Context,"
+        errorLine2="    ~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt"
+            line="61"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Resources is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Main private val resources: Resources,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepository.kt"
+            line="39"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Resources is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Main private val resources: Resources,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt"
+            line="37"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Resources is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Main private val resources: Resources,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/FaceHelpMessageDeferral.kt"
+            line="42"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Context is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="            @NonNull final Context context,"
+        errorLine2="                                   ~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java"
+            line="186"
+            column="36"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Context is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="class IconBuilder @Inject constructor(private val context: Context) {"
+        errorLine2="                                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt"
+            line="27"
+            column="39"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window should use ShadeDisplayAware-annotated WindowManager, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of WindowManager is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    private val windowManager: WindowManager,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt"
+            line="40"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Resources is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Main resources: Resources,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt"
+            line="53"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window should use ShadeDisplayAware-annotated Resources, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Resources is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    @Main private val resources: Resources,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddable.kt"
+            line="51"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window should use ShadeDisplayAware-annotated Context, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of Context is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    private val context: Context,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt"
+            line="51"
+            column="5"/>
+    </issue>
+
+    <issue
+        id="ShadeDisplayAwareContextChecker"
+        message="UI elements of the shade window should use ShadeDisplayAware-annotated WindowManager, as the shade might move between windows, and only @ShadeDisplayAware resources are updated with the new configuration correctly. Failures to do so might result in wrong dimensions for shade window classes (e.g. using the wrong density or theme). If the usage of WindowManager is not related to display specific configuration or UI, then there is technically no need to use the annotation, and you can annotate the class with @SuppressLint(&quot;ShadeDisplayAwareContextChecker&quot;)/@Suppress(&quot;ShadeDisplayAwareContextChecker&quot;)"
+        errorLine1="    windowManager: WindowManager,"
+        errorLine2="    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/base/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt"
+            line="53"
+            column="5"/>
 
 </issues>
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayRegistrantTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayRegistrantTest.kt
index 790df03..1e937b4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayRegistrantTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayRegistrantTest.kt
@@ -29,8 +29,11 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
+import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled
 import com.android.systemui.log.core.FakeLogBuffer
 import com.android.systemui.shared.condition.Monitor
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.withArgCaptor
 import kotlin.test.Test
 import org.junit.Before
@@ -48,6 +51,8 @@
 @TestableLooper.RunWithLooper
 @RunWith(AndroidJUnit4::class)
 class DreamOverlayRegistrantTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+
     private val context = mock<Context>()
 
     private val packageManager = mock<PackageManager>()
@@ -73,6 +78,7 @@
                 monitor,
                 packageManager,
                 dreamManager,
+                kosmos.communalSettingsInteractor,
                 logBuffer,
             )
 
@@ -117,7 +123,7 @@
 
     /** Verify overlay registered when enabled in manifest. */
     @Test
-    @DisableFlags(Flags.FLAG_COMMUNAL_HUB_ON_MOBILE)
+    @DisableFlags(Flags.FLAG_GLANCEABLE_HUB_V2)
     fun testRegisteredWhenEnabledWithManifest() {
         serviceInfo.enabled = true
         start()
@@ -127,8 +133,10 @@
 
     /** Verify overlay registered for mobile hub with flag. */
     @Test
-    @EnableFlags(Flags.FLAG_COMMUNAL_HUB_ON_MOBILE)
+    @EnableFlags(Flags.FLAG_GLANCEABLE_HUB_V2)
     fun testRegisteredForMobileHub() {
+        kosmos.setCommunalV2ConfigEnabled(true)
+
         start()
 
         verify(dreamManager).registerDreamOverlayService(componentName)
@@ -139,7 +147,7 @@
      * enabled.
      */
     @Test
-    @DisableFlags(Flags.FLAG_COMMUNAL_HUB_ON_MOBILE)
+    @DisableFlags(Flags.FLAG_GLANCEABLE_HUB_V2)
     fun testDisabledForMobileWithoutMobileHub() {
         start()
 
@@ -154,8 +162,9 @@
 
     /** Ensure service unregistered when component is disabled at runtime. */
     @Test
-    @EnableFlags(Flags.FLAG_COMMUNAL_HUB_ON_MOBILE)
+    @EnableFlags(Flags.FLAG_GLANCEABLE_HUB_V2)
     fun testUnregisteredWhenComponentDisabled() {
+        kosmos.setCommunalV2ConfigEnabled(true)
         start()
         verify(dreamManager).registerDreamOverlayService(componentName)
         clearInvocations(dreamManager)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
index f924ccb..b07097d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -43,7 +43,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
-import com.android.systemui.Flags.FLAG_COMMUNAL_HUB_ON_MOBILE
+import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
 import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.ambient.touch.TouchHandler
@@ -55,7 +55,9 @@
 import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
 import com.android.systemui.communal.domain.interactor.setCommunalAvailable
+import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled
 import com.android.systemui.communal.shared.log.CommunalUiEvent
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.complication.ComplicationHostViewController
@@ -262,6 +264,7 @@
                 mKeyguardUpdateMonitor,
                 mScrimManager,
                 mCommunalInteractor,
+                kosmos.communalSettingsInteractor,
                 kosmos.sceneInteractor,
                 mSystemDialogsCloser,
                 mUiEventLogger,
@@ -1283,7 +1286,7 @@
         environmentComponents.verifyNoMoreInteractions()
     }
 
-    @DisableFlags(FLAG_COMMUNAL_HUB_ON_MOBILE)
+    @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
     @Test
     fun testAmbientTouchHandlersRegistration_registerHideComplicationAndCommunal() {
         val client = client
@@ -1303,9 +1306,11 @@
             .containsExactly(mHideComplicationTouchHandler, mCommunalTouchHandler)
     }
 
-    @EnableFlags(FLAG_COMMUNAL_HUB_ON_MOBILE)
+    @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
     @Test
     fun testAmbientTouchHandlersRegistration_v2_registerOnlyHideComplication() {
+        kosmos.setCommunalV2ConfigEnabled(true)
+
         val client = client
 
         // Inform the overlay service of dream starting.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt
index 76434ee..1de38ee 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt
@@ -16,10 +16,8 @@
 
 package com.android.systemui.inputdevice.tutorial.ui.viewmodel
 
-import androidx.lifecycle.Lifecycle.Event
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.LifecycleRegistry
 import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.testing.TestLifecycleOwner
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -55,7 +53,6 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mockito.mock
 import org.mockito.kotlin.mock
 
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -71,9 +68,6 @@
     private var tutorialScope = INTENT_TUTORIAL_SCOPE_TOUCHPAD
     private val viewModel by lazy { createViewModel(tutorialScope) }
 
-    // createUnsafe so its methods don't have to be called on Main thread
-    private val lifecycle = LifecycleRegistry.createUnsafe(mock(LifecycleOwner::class.java))
-
     @get:Rule val mainDispatcherRule = MainDispatcherRule(kosmos.testDispatcher)
 
     private fun createViewModel(
@@ -88,7 +82,6 @@
                 mock<InputDeviceTutorialLogger>(),
                 SavedStateHandle(mapOf(INTENT_TUTORIAL_SCOPE_KEY to scope)),
             )
-        lifecycle.addObserver(viewModel)
         return viewModel
     }
 
@@ -279,7 +272,7 @@
             collectValues(viewModel.screen) // just to initialize viewModel
             peripheralsState(touchpadConnected = true)
 
-            lifecycle.handleLifecycleEvent(Event.ON_START)
+            viewModel.onStart(TestLifecycleOwner())
 
             assertGesturesDisabled()
         }
@@ -291,8 +284,8 @@
             collectValues(viewModel.screen)
             peripheralsState(touchpadConnected = true)
 
-            lifecycle.handleLifecycleEvent(Event.ON_START)
-            lifecycle.handleLifecycleEvent(Event.ON_STOP)
+            viewModel.onStart(TestLifecycleOwner())
+            viewModel.onStop(TestLifecycleOwner())
 
             assertGesturesNotDisabled()
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfigTest.kt
index 77c615c..789b10b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfigTest.kt
@@ -25,7 +25,8 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.data.repository.communalSceneRepository
 import com.android.systemui.communal.domain.interactor.communalInteractor
-import com.android.systemui.communal.domain.interactor.setCommunalEnabled
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
+import com.android.systemui.communal.domain.interactor.setCommunalV2Enabled
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.andSceneContainer
@@ -38,7 +39,6 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
-import org.junit.Assert.assertTrue
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -48,7 +48,7 @@
 
 @SmallTest
 @OptIn(ExperimentalCoroutinesApi::class)
-@EnableFlags(Flags.FLAG_GLANCEABLE_HUB_SHORTCUT_BUTTON)
+@EnableFlags(Flags.FLAG_GLANCEABLE_HUB_SHORTCUT_BUTTON, Flags.FLAG_GLANCEABLE_HUB_V2)
 @RunWith(ParameterizedAndroidJunit4::class)
 class GlanceableHubQuickAffordanceConfigTest(flags: FlagsParameterization?) : SysuiTestCase() {
     private val kosmos = testKosmos()
@@ -69,6 +69,7 @@
                 context = context,
                 communalInteractor = kosmos.communalInteractor,
                 communalSceneRepository = kosmos.communalSceneRepository,
+                communalSettingsInteractor = kosmos.communalSettingsInteractor,
                 sceneInteractor = kosmos.sceneInteractor,
             )
     }
@@ -76,28 +77,30 @@
     @Test
     fun lockscreenState_whenGlanceableHubEnabled_returnsVisible() =
         testScope.runTest {
-            kosmos.setCommunalEnabled(true)
+            kosmos.setCommunalV2Enabled(true)
             runCurrent()
 
             val lockScreenState by collectLastValue(underTest.lockScreenState)
 
-            assertTrue(lockScreenState is KeyguardQuickAffordanceConfig.LockScreenState.Visible)
+            assertThat(lockScreenState)
+                .isInstanceOf(KeyguardQuickAffordanceConfig.LockScreenState.Visible::class.java)
         }
 
     @Test
     fun lockscreenState_whenGlanceableHubDisabled_returnsHidden() =
         testScope.runTest {
-            kosmos.setCommunalEnabled(false)
+            kosmos.setCommunalV2Enabled(false)
             val lockScreenState by collectLastValue(underTest.lockScreenState)
             runCurrent()
 
-            assertTrue(lockScreenState is KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
+            assertThat(lockScreenState)
+                .isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
         }
 
     @Test
     fun pickerScreenState_whenGlanceableHubEnabled_returnsDefault() =
         testScope.runTest {
-            kosmos.setCommunalEnabled(true)
+            kosmos.setCommunalV2Enabled(true)
             runCurrent()
 
             assertThat(underTest.getPickerScreenState())
@@ -107,7 +110,7 @@
     @Test
     fun pickerScreenState_whenGlanceableHubDisabled_returnsDisabled() =
         testScope.runTest {
-            kosmos.setCommunalEnabled(false)
+            kosmos.setCommunalV2Enabled(false)
             runCurrent()
 
             assertThat(
@@ -143,7 +146,8 @@
         @Parameters(name = "{0}")
         fun getParams(): List<FlagsParameterization> {
             return FlagsParameterization.allCombinationsOf(
-                    Flags.FLAG_GLANCEABLE_HUB_SHORTCUT_BUTTON
+                    Flags.FLAG_GLANCEABLE_HUB_SHORTCUT_BUTTON,
+                    Flags.FLAG_GLANCEABLE_HUB_V2,
                 )
                 .andSceneContainer()
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryTest.kt
new file mode 100644
index 0000000..eef195b
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryTest.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.qs.panels.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
+import com.android.systemui.qs.panels.shared.model.PaginatedGridLayoutType
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class GridLayoutTypeRepositoryTest : SysuiTestCase() {
+    val kosmos = testKosmos()
+
+    val underTest = kosmos.gridLayoutTypeRepository
+
+    @Test
+    fun defaultType_paginated() =
+        kosmos.runTest {
+            val type by collectLastValue(underTest.defaultLayoutType)
+
+            assertThat(type).isEqualTo(PaginatedGridLayoutType)
+        }
+
+    @Test
+    fun dualShadeType_infinite() =
+        kosmos.runTest {
+            val type by collectLastValue(underTest.dualShadeLayoutType)
+
+            assertThat(type).isEqualTo(InfiniteGridLayoutType)
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorTest.kt
new file mode 100644
index 0000000..b591538
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorTest.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.qs.panels.domain.interactor
+
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
+import com.android.systemui.qs.panels.shared.model.PaginatedGridLayoutType
+import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.shade.shared.flag.DualShade
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class GridLayoutTypeInteractorTest : SysuiTestCase() {
+    val kosmos = testKosmos()
+
+    val Kosmos.underTest by Kosmos.Fixture { kosmos.gridLayoutTypeInteractor }
+
+    @DisableFlags(DualShade.FLAG_NAME)
+    @Test
+    fun noDualShade_gridAlwaysPaginated() =
+        kosmos.runTest {
+            val type by collectLastValue(underTest.layout)
+
+            fakeShadeRepository.setShadeLayoutWide(false)
+            assertThat(type).isEqualTo(PaginatedGridLayoutType)
+
+            fakeShadeRepository.setShadeLayoutWide(true)
+            assertThat(type).isEqualTo(PaginatedGridLayoutType)
+        }
+
+    @EnableFlags(DualShade.FLAG_NAME)
+    @Test
+    fun dualShade_gridAlwaysInfinite() =
+        kosmos.runTest {
+            val type by collectLastValue(underTest.layout)
+
+            fakeShadeRepository.setShadeLayoutWide(false)
+            assertThat(type).isEqualTo(InfiniteGridLayoutType)
+
+            fakeShadeRepository.setShadeLayoutWide(true)
+            assertThat(type).isEqualTo(InfiniteGridLayoutType)
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt
index a9a527f..4891c9f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt
@@ -22,7 +22,7 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
 import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository
-import com.android.systemui.qs.panels.domain.interactor.infiniteGridLayout
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.infiniteGridLayout
 import com.android.systemui.qs.panels.ui.viewmodel.MockTileViewModel
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.testKosmos
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/EditModeButtonViewModelTest.kt
similarity index 95%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/EditModeButtonViewModelTest.kt
index f2bfd72..a8e390c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/EditModeButtonViewModelTest.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.kosmos.collectLastValue
 import com.android.systemui.kosmos.runCurrent
 import com.android.systemui.kosmos.runTest
+import com.android.systemui.qs.panels.ui.viewmodel.toolbar.editModeButtonViewModelFactory
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
index 3ebf9f7..a62d9d5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.statusbar.chips.ui.model.ColorsModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
 import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
 import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
 import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel
@@ -163,6 +164,28 @@
         }
 
     @Test
+    @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON, StatusBarConnectedDisplays.FLAG_NAME)
+    fun chip_positiveStartTime_notifIconAndConnectedDisplaysFlagOn_iconIsNotifIcon() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.chip)
+
+            val notifKey = "testNotifKey"
+            repo.setOngoingCallState(
+                inCallModel(startTimeMs = 1000, notificationIcon = null, notificationKey = notifKey)
+            )
+
+            assertThat((latest as OngoingActivityChipModel.Shown).icon)
+                .isInstanceOf(
+                    OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon::class.java
+                )
+            val actualNotifKey =
+                (((latest as OngoingActivityChipModel.Shown).icon)
+                        as OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon)
+                    .notificationKey
+            assertThat(actualNotifKey).isEqualTo(notifKey)
+        }
+
+    @Test
     @DisableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON)
     fun chip_zeroStartTime_notifIconFlagOff_iconIsPhone() =
         testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt
index e96dd16..f06bab7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.chips.notification.domain.interactor
 
+import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -25,6 +26,8 @@
 import com.android.systemui.kosmos.runTest
 import com.android.systemui.kosmos.useUnconfinedTestDispatcher
 import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.chips.notification.domain.model.NotificationChipModel
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
 import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
@@ -111,6 +114,27 @@
         }
 
     @Test
+    @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+    fun notificationChip_cdEnabled_missingStatusBarIconChipView_inConstructor_emitsNotNull() =
+        kosmos.runTest {
+            val underTest =
+                factory.create(
+                    activeNotificationModel(
+                        key = "notif1",
+                        statusBarChipIcon = null,
+                        whenTime = 123L,
+                    )
+                )
+
+            val latest by collectLastValue(underTest.notificationChip)
+
+            assertThat(latest)
+                .isEqualTo(
+                    NotificationChipModel("notif1", statusBarChipIconView = null, whenTime = 123L)
+                )
+        }
+
+    @Test
     fun notificationChip_missingStatusBarIconChipView_inSet_emitsNull() =
         kosmos.runTest {
             val startingNotif = activeNotificationModel(key = "notif1", statusBarChipIcon = mock())
@@ -126,6 +150,29 @@
         }
 
     @Test
+    @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+    fun notificationChip_missingStatusBarIconChipView_inSet_cdEnabled_emitsNotNull() =
+        kosmos.runTest {
+            val startingNotif = activeNotificationModel(key = "notif1", statusBarChipIcon = mock())
+            val underTest = factory.create(startingNotif)
+            val latest by collectLastValue(underTest.notificationChip)
+            assertThat(latest).isNotNull()
+
+            underTest.setNotification(
+                activeNotificationModel(key = "notif1", statusBarChipIcon = null, whenTime = 123L)
+            )
+
+            assertThat(latest)
+                .isEqualTo(
+                    NotificationChipModel(
+                        key = "notif1",
+                        statusBarChipIconView = null,
+                        whenTime = 123L,
+                    )
+                )
+        }
+
+    @Test
     fun notificationChip_appIsVisibleOnCreation_emitsNull() =
         kosmos.runTest {
             activityManagerRepository.fake.startingIsAppVisibleValue = true
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
index 11831ca..8a4ddce 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor
 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
 import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
 import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
@@ -107,6 +108,30 @@
         }
 
     @Test
+    @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+    fun chips_onePromotedNotif_connectedDisplaysFlagEnabled_statusBarIconMatches() =
+        kosmos.runTest {
+            val latest by collectLastValue(underTest.chips)
+
+            val notifKey = "notif"
+            setNotifs(
+                listOf(
+                    activeNotificationModel(
+                        key = notifKey,
+                        statusBarChipIcon = null,
+                        promotedContent = PromotedNotificationContentModel.Builder(notifKey).build(),
+                    )
+                )
+            )
+
+            assertThat(latest).hasSize(1)
+            val chip = latest!![0]
+            assertThat(chip).isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java)
+            assertThat(chip.icon)
+                .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(notifKey))
+        }
+
+    @Test
     fun chips_onlyForPromotedNotifs() =
         kosmos.runTest {
             val latest by collectLastValue(underTest.chips)
@@ -139,6 +164,41 @@
         }
 
     @Test
+    @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+    fun chips_connectedDisplaysFlagEnabled_onlyForPromotedNotifs() =
+        kosmos.runTest {
+            val latest by collectLastValue(underTest.chips)
+
+            val firstKey = "notif1"
+            val secondKey = "notif2"
+            val thirdKey = "notif3"
+            setNotifs(
+                listOf(
+                    activeNotificationModel(
+                        key = firstKey,
+                        statusBarChipIcon = null,
+                        promotedContent = PromotedNotificationContentModel.Builder(firstKey).build(),
+                    ),
+                    activeNotificationModel(
+                        key = secondKey,
+                        statusBarChipIcon = null,
+                        promotedContent =
+                            PromotedNotificationContentModel.Builder(secondKey).build(),
+                    ),
+                    activeNotificationModel(
+                        key = thirdKey,
+                        statusBarChipIcon = null,
+                        promotedContent = null,
+                    ),
+                )
+            )
+
+            assertThat(latest).hasSize(2)
+            assertIsNotifKey(latest!![0], firstKey)
+            assertIsNotifKey(latest!![1], secondKey)
+        }
+
+    @Test
     fun chips_clickingChipNotifiesInteractor() =
         kosmos.runTest {
             val latest by collectLastValue(underTest.chips)
@@ -178,5 +238,12 @@
             assertThat((latest as OngoingActivityChipModel.Shown).icon)
                 .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarView(expectedIcon))
         }
+
+        fun assertIsNotifKey(latest: OngoingActivityChipModel?, expectedKey: String) {
+            assertThat(latest)
+                .isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java)
+            assertThat((latest as OngoingActivityChipModel.Shown).icon)
+                .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(expectedKey))
+        }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
index 689fc7c..68798a8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package com.android.systemui.statusbar.notification.collection.coordinator
 
 import android.app.Notification
@@ -22,83 +24,92 @@
 import android.os.UserHandle
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
 import android.service.notification.StatusBarNotification
-import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.keyguardUpdateMonitor
 import com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
-import com.android.systemui.kosmos.applicationCoroutineScope
-import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.flags.andSceneContainer
+import com.android.systemui.plugins.statusbar.fakeStatusBarStateController
+import com.android.systemui.plugins.statusbar.statusBarStateController
 import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.RankingBuilder
 import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.notification.DynamicPrivacyController
 import com.android.systemui.statusbar.notification.collection.ListEntry
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
-import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
+import com.android.systemui.statusbar.notification.collection.notifPipeline
+import com.android.systemui.statusbar.notification.dynamicPrivacyController
+import com.android.systemui.statusbar.notification.mockDynamicPrivacyController
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
-import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.notificationLockscreenUserManager
 import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController
+import com.android.systemui.statusbar.policy.mockSensitiveNotificationProtectionController
+import com.android.systemui.statusbar.policy.sensitiveNotificationProtectionController
 import com.android.systemui.testKosmos
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.withArgCaptor
-import dagger.BindsInstance
-import dagger.Component
-import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @SmallTest
-@RunWith(AndroidJUnit4::class)
-class SensitiveContentCoordinatorTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class SensitiveContentCoordinatorTest(flags: FlagsParameterization) : SysuiTestCase() {
 
-    val kosmos = testKosmos()
+    val kosmos =
+        testKosmos().apply {
+            // Override some Kosmos objects with mocks or fakes for easier testability
+            dynamicPrivacyController = mockDynamicPrivacyController
+            sensitiveNotificationProtectionController =
+                mockSensitiveNotificationProtectionController
+            statusBarStateController = fakeStatusBarStateController
+        }
 
-    val dynamicPrivacyController: DynamicPrivacyController = mock()
-    val lockscreenUserManager: NotificationLockscreenUserManager = mock()
-    val pipeline: NotifPipeline = mock()
-    val keyguardUpdateMonitor: KeyguardUpdateMonitor = mock()
-    val statusBarStateController: StatusBarStateController = mock()
-    val keyguardStateController: KeyguardStateController = mock()
-    val mSelectedUserInteractor: SelectedUserInteractor = mock()
+    val dynamicPrivacyController: DynamicPrivacyController = kosmos.mockDynamicPrivacyController
+    val lockscreenUserManager: NotificationLockscreenUserManager =
+        kosmos.notificationLockscreenUserManager
+    val pipeline: NotifPipeline = kosmos.notifPipeline
+    val keyguardUpdateMonitor: KeyguardUpdateMonitor = kosmos.keyguardUpdateMonitor
+    val statusBarStateController: SysuiStatusBarStateController =
+        kosmos.fakeStatusBarStateController
     val sensitiveNotificationProtectionController: SensitiveNotificationProtectionController =
-        mock()
-    val deviceEntryInteractor: DeviceEntryInteractor = mock()
-    val sceneInteractor: SceneInteractor = mock()
+        kosmos.mockSensitiveNotificationProtectionController
+    val sceneInteractor: SceneInteractor = kosmos.sceneInteractor
 
-    val coordinator: SensitiveContentCoordinator =
-        DaggerTestSensitiveContentCoordinatorComponent.factory()
-            .create(
-                dynamicPrivacyController,
-                lockscreenUserManager,
-                keyguardUpdateMonitor,
-                statusBarStateController,
-                keyguardStateController,
-                mSelectedUserInteractor,
-                sensitiveNotificationProtectionController,
-                deviceEntryInteractor,
-                sceneInteractor,
-                kosmos.applicationCoroutineScope,
-            )
-            .coordinator
+    val coordinator: SensitiveContentCoordinator by lazy { kosmos.sensitiveContentCoordinator }
+
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
 
     @Test
     fun onDynamicPrivacyChanged_invokeInvalidationListener() {
@@ -143,7 +154,7 @@
     fun screenshareSecretFilter_flagDisabled_filterNoAdded() {
         coordinator.attach(pipeline)
 
-        verify(pipeline, never()).addFinalizeFilter(any(NotifFilter::class.java))
+        verify(pipeline, never()).addFinalizeFilter(any())
     }
 
     @Test
@@ -675,13 +686,13 @@
         whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
         whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
         whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
-        whenever(statusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD)
         whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(any()))
             .thenReturn(true)
         val entry = fakeNotification(2, true)
         whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
         whenever(sensitiveNotificationProtectionController.shouldProtectNotification(any()))
             .thenReturn(true)
+        statusBarStateController.state = StatusBarState.KEYGUARD
 
         onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
 
@@ -733,26 +744,3 @@
         return notificationEntry
     }
 }
-
-@CoordinatorScope
-@Component(modules = [SensitiveContentCoordinatorModule::class])
-interface TestSensitiveContentCoordinatorComponent {
-    val coordinator: SensitiveContentCoordinator
-
-    @Component.Factory
-    interface Factory {
-        fun create(
-            @BindsInstance dynamicPrivacyController: DynamicPrivacyController,
-            @BindsInstance lockscreenUserManager: NotificationLockscreenUserManager,
-            @BindsInstance keyguardUpdateMonitor: KeyguardUpdateMonitor,
-            @BindsInstance statusBarStateController: StatusBarStateController,
-            @BindsInstance keyguardStateController: KeyguardStateController,
-            @BindsInstance selectedUserInteractor: SelectedUserInteractor,
-            @BindsInstance
-            sensitiveNotificationProtectionController: SensitiveNotificationProtectionController,
-            @BindsInstance deviceEntryInteractor: DeviceEntryInteractor,
-            @BindsInstance sceneInteractor: SceneInteractor,
-            @BindsInstance @Application scope: CoroutineScope,
-        ): TestSensitiveContentCoordinatorComponent
-    }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplOldTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplOldTest.kt
deleted file mode 100644
index 8b4f53a..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplOldTest.kt
+++ /dev/null
@@ -1,698 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.statusbar.notification.headsup
-
-import android.app.Notification
-import android.app.PendingIntent
-import android.app.Person
-import android.os.Handler
-import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.FlagsParameterization
-import android.testing.TestableLooper.RunWithLooper
-import androidx.test.filters.SmallTest
-import com.android.internal.logging.testing.UiEventLoggerFake
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.kosmos.KosmosJavaAdapter
-import com.android.systemui.log.logcatLogBuffer
-import com.android.systemui.res.R
-import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
-import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManagerImpl
-import com.android.systemui.statusbar.notification.headsup.HeadsUpManagerImpl.HeadsUpEntry
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
-import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
-import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.concurrency.mockExecutorHandler
-import com.android.systemui.util.kotlin.JavaAdapter
-import com.android.systemui.util.settings.FakeGlobalSettings
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.MutableStateFlow
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers
-import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.invocation.InvocationOnMock
-import org.mockito.junit.MockitoJUnit
-import org.mockito.junit.MockitoRule
-import org.mockito.kotlin.eq
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4
-import platform.test.runner.parameterized.Parameters
-
-@SmallTest
-@RunWithLooper
-@RunWith(ParameterizedAndroidJunit4::class)
-// TODO(b/378142453): Merge this with HeadsUpManagerImplTest.
-open class HeadsUpManagerImplOldTest(flags: FlagsParameterization?) : SysuiTestCase() {
-    protected var mKosmos: KosmosJavaAdapter = KosmosJavaAdapter(this)
-
-    @JvmField @Rule var rule: MockitoRule = MockitoJUnit.rule()
-
-    private val mUiEventLoggerFake = UiEventLoggerFake()
-
-    private val mLogger: HeadsUpManagerLogger = Mockito.spy(HeadsUpManagerLogger(logcatLogBuffer()))
-
-    @Mock private val mBgHandler: Handler? = null
-
-    @Mock private val dumpManager: DumpManager? = null
-
-    @Mock private val mShadeInteractor: ShadeInteractor? = null
-    private var mAvalancheController: AvalancheController? = null
-
-    @Mock private val mAccessibilityMgr: AccessibilityManagerWrapper? = null
-
-    protected val globalSettings: FakeGlobalSettings = FakeGlobalSettings()
-    protected val systemClock: FakeSystemClock = FakeSystemClock()
-    protected val executor: FakeExecutor = FakeExecutor(systemClock)
-
-    @Mock protected var mRow: ExpandableNotificationRow? = null
-
-    private fun createHeadsUpManager(): HeadsUpManagerImpl {
-        return HeadsUpManagerImpl(
-            mContext,
-            mLogger,
-            mKosmos.statusBarStateController,
-            mKosmos.keyguardBypassController,
-            GroupMembershipManagerImpl(),
-            mKosmos.visualStabilityProvider,
-            mKosmos.configurationController,
-            mockExecutorHandler(executor),
-            globalSettings,
-            systemClock,
-            executor,
-            mAccessibilityMgr,
-            mUiEventLoggerFake,
-            JavaAdapter(mKosmos.testScope),
-            mShadeInteractor,
-            mAvalancheController,
-        )
-    }
-
-    private fun createStickyEntry(id: Int): NotificationEntry {
-        val notif =
-            Notification.Builder(mContext, "")
-                .setSmallIcon(R.drawable.ic_person)
-                .setFullScreenIntent(
-                    Mockito.mock(PendingIntent::class.java), /* highPriority */
-                    true,
-                )
-                .build()
-        return HeadsUpManagerTestUtil.createEntry(id, notif)
-    }
-
-    private fun createStickyForSomeTimeEntry(id: Int): NotificationEntry {
-        val notif =
-            Notification.Builder(mContext, "")
-                .setSmallIcon(R.drawable.ic_person)
-                .setFlag(Notification.FLAG_FSI_REQUESTED_BUT_DENIED, true)
-                .build()
-        return HeadsUpManagerTestUtil.createEntry(id, notif)
-    }
-
-    private fun useAccessibilityTimeout(use: Boolean) {
-        if (use) {
-            Mockito.doReturn(TEST_A11Y_AUTO_DISMISS_TIME)
-                .`when`(mAccessibilityMgr!!)
-                .getRecommendedTimeoutMillis(ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt())
-        } else {
-            Mockito.`when`(
-                    mAccessibilityMgr!!.getRecommendedTimeoutMillis(
-                        ArgumentMatchers.anyInt(),
-                        ArgumentMatchers.anyInt(),
-                    )
-                )
-                .then { i: InvocationOnMock -> i.getArgument(0) }
-        }
-    }
-
-    init {
-        mSetFlagsRule.setFlagsParameterization(flags!!)
-    }
-
-    @Throws(Exception::class)
-    override fun SysuiSetup() {
-        super.SysuiSetup()
-        mContext.getOrCreateTestableResources().apply {
-            this.addOverride(R.integer.ambient_notification_extension_time, TEST_EXTENSION_TIME)
-            this.addOverride(R.integer.touch_acceptance_delay, TEST_TOUCH_ACCEPTANCE_TIME)
-            this.addOverride(
-                R.integer.heads_up_notification_minimum_time,
-                TEST_MINIMUM_DISPLAY_TIME,
-            )
-            this.addOverride(
-                R.integer.heads_up_notification_minimum_time_with_throttling,
-                TEST_MINIMUM_DISPLAY_TIME,
-            )
-            this.addOverride(R.integer.heads_up_notification_decay, TEST_AUTO_DISMISS_TIME)
-            this.addOverride(
-                R.integer.sticky_heads_up_notification_time,
-                TEST_STICKY_AUTO_DISMISS_TIME,
-            )
-        }
-
-        mAvalancheController =
-            AvalancheController(dumpManager!!, mUiEventLoggerFake, mLogger, mBgHandler!!)
-        Mockito.`when`(mShadeInteractor!!.isAnyExpanded).thenReturn(MutableStateFlow(true))
-        Mockito.`when`(mKosmos.keyguardBypassController.bypassEnabled).thenReturn(false)
-    }
-
-    @Test
-    fun testHasNotifications_headsUpManagerMapNotEmpty_true() {
-        val bhum = createHeadsUpManager()
-        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
-        bhum.showNotification(entry)
-
-        Truth.assertThat(bhum.mHeadsUpEntryMap).isNotEmpty()
-        Truth.assertThat(bhum.hasNotifications()).isTrue()
-    }
-
-    @Test
-    @EnableFlags(NotificationThrottleHun.FLAG_NAME)
-    fun testHasNotifications_avalancheMapNotEmpty_true() {
-        val bhum = createHeadsUpManager()
-        val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
-        val headsUpEntry = bhum.createHeadsUpEntry(notifEntry)
-        mAvalancheController!!.addToNext(headsUpEntry) {}
-
-        Truth.assertThat(mAvalancheController!!.getWaitingEntryList()).isNotEmpty()
-        Truth.assertThat(bhum.hasNotifications()).isTrue()
-    }
-
-    @Test
-    @EnableFlags(NotificationThrottleHun.FLAG_NAME)
-    fun testHasNotifications_false() {
-        val bhum = createHeadsUpManager()
-        Truth.assertThat(bhum.mHeadsUpEntryMap).isEmpty()
-        Truth.assertThat(mAvalancheController!!.getWaitingEntryList()).isEmpty()
-        Truth.assertThat(bhum.hasNotifications()).isFalse()
-    }
-
-    @Test
-    @EnableFlags(NotificationThrottleHun.FLAG_NAME)
-    fun testGetHeadsUpEntryList_includesAvalancheEntryList() {
-        val bhum = createHeadsUpManager()
-        val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
-        val headsUpEntry = bhum.createHeadsUpEntry(notifEntry)
-        mAvalancheController!!.addToNext(headsUpEntry) {}
-
-        Truth.assertThat(bhum.headsUpEntryList).contains(headsUpEntry)
-    }
-
-    @Test
-    @EnableFlags(NotificationThrottleHun.FLAG_NAME)
-    fun testGetHeadsUpEntry_returnsAvalancheEntry() {
-        val bhum = createHeadsUpManager()
-        val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
-        val headsUpEntry = bhum.createHeadsUpEntry(notifEntry)
-        mAvalancheController!!.addToNext(headsUpEntry) {}
-
-        Truth.assertThat(bhum.getHeadsUpEntry(notifEntry.key)).isEqualTo(headsUpEntry)
-    }
-
-    @Test
-    fun testShowNotification_addsEntry() {
-        val alm = createHeadsUpManager()
-        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
-
-        alm.showNotification(entry)
-
-        assertThat(alm.isHeadsUpEntry(entry.key)).isTrue()
-        assertThat(alm.hasNotifications()).isTrue()
-        assertThat(alm.getEntry(entry.key)).isEqualTo(entry)
-    }
-
-    @Test
-    fun testShowNotification_autoDismisses() {
-        val alm = createHeadsUpManager()
-        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
-
-        alm.showNotification(entry)
-        systemClock.advanceTime((TEST_AUTO_DISMISS_TIME * 3 / 2).toLong())
-
-        assertThat(alm.isHeadsUpEntry(entry.key)).isFalse()
-    }
-
-    @Test
-    fun testRemoveNotification_removeDeferred() {
-        val alm = createHeadsUpManager()
-        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
-
-        alm.showNotification(entry)
-
-        val removedImmediately =
-            alm.removeNotification(entry.key, /* releaseImmediately= */ false, "removeDeferred")
-        assertThat(removedImmediately).isFalse()
-        assertThat(alm.isHeadsUpEntry(entry.key)).isTrue()
-    }
-
-    @Test
-    fun testRemoveNotification_forceRemove() {
-        val alm = createHeadsUpManager()
-        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
-
-        alm.showNotification(entry)
-
-        val removedImmediately =
-            alm.removeNotification(entry.key, /* releaseImmediately= */ true, "forceRemove")
-        assertThat(removedImmediately).isTrue()
-        assertThat(alm.isHeadsUpEntry(entry.key)).isFalse()
-    }
-
-    @Test
-    fun testReleaseAllImmediately() {
-        val alm = createHeadsUpManager()
-        for (i in 0 until TEST_NUM_NOTIFICATIONS) {
-            val entry = HeadsUpManagerTestUtil.createEntry(i, mContext)
-            entry.row = mRow
-            alm.showNotification(entry)
-        }
-
-        alm.releaseAllImmediately()
-
-        assertThat(alm.allEntries.count()).isEqualTo(0)
-    }
-
-    @Test
-    fun testCanRemoveImmediately_notShownLongEnough() {
-        val alm = createHeadsUpManager()
-        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
-
-        alm.showNotification(entry)
-
-        // The entry has just been added so we should not remove immediately.
-        assertThat(alm.canRemoveImmediately(entry.key)).isFalse()
-    }
-
-    @Test
-    fun testHunRemovedLogging() {
-        val hum = createHeadsUpManager()
-        val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
-        val headsUpEntry = Mockito.mock(HeadsUpEntry::class.java)
-        Mockito.`when`(headsUpEntry.pinnedStatus)
-            .thenReturn(MutableStateFlow(PinnedStatus.NotPinned))
-        headsUpEntry.mEntry = notifEntry
-
-        hum.onEntryRemoved(headsUpEntry, "test")
-
-        Mockito.verify(mLogger, Mockito.times(1)).logNotificationActuallyRemoved(eq(notifEntry))
-    }
-
-    @Test
-    fun testShowNotification_autoDismissesIncludingTouchAcceptanceDelay() {
-        val hum = createHeadsUpManager()
-        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
-        useAccessibilityTimeout(false)
-
-        hum.showNotification(entry)
-        systemClock.advanceTime((TEST_TOUCH_ACCEPTANCE_TIME / 2 + TEST_AUTO_DISMISS_TIME).toLong())
-
-        assertThat(hum.isHeadsUpEntry(entry.key)).isTrue()
-    }
-
-    @Test
-    fun testShowNotification_autoDismissesWithDefaultTimeout() {
-        val hum = createHeadsUpManager()
-        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
-        useAccessibilityTimeout(false)
-
-        hum.showNotification(entry)
-        systemClock.advanceTime(
-            (TEST_TOUCH_ACCEPTANCE_TIME +
-                    (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2)
-                .toLong()
-        )
-
-        assertThat(hum.isHeadsUpEntry(entry.key)).isFalse()
-    }
-
-    @Test
-    fun testShowNotification_stickyForSomeTime_autoDismissesWithStickyTimeout() {
-        val hum = createHeadsUpManager()
-        val entry = createStickyForSomeTimeEntry(/* id= */ 0)
-        useAccessibilityTimeout(false)
-
-        hum.showNotification(entry)
-        systemClock.advanceTime(
-            (TEST_TOUCH_ACCEPTANCE_TIME +
-                    (TEST_AUTO_DISMISS_TIME + TEST_STICKY_AUTO_DISMISS_TIME) / 2)
-                .toLong()
-        )
-
-        assertThat(hum.isHeadsUpEntry(entry.key)).isTrue()
-    }
-
-    @Test
-    fun testShowNotification_sticky_neverAutoDismisses() {
-        val hum = createHeadsUpManager()
-        val entry = createStickyEntry(/* id= */ 0)
-        useAccessibilityTimeout(false)
-
-        hum.showNotification(entry)
-        systemClock.advanceTime(
-            (TEST_TOUCH_ACCEPTANCE_TIME + 2 * TEST_A11Y_AUTO_DISMISS_TIME).toLong()
-        )
-
-        assertThat(hum.isHeadsUpEntry(entry.key)).isTrue()
-    }
-
-    @Test
-    fun testShowNotification_autoDismissesWithAccessibilityTimeout() {
-        val hum = createHeadsUpManager()
-        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
-        useAccessibilityTimeout(true)
-
-        hum.showNotification(entry)
-        systemClock.advanceTime(
-            (TEST_TOUCH_ACCEPTANCE_TIME +
-                    (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2)
-                .toLong()
-        )
-
-        assertThat(hum.isHeadsUpEntry(entry.key)).isTrue()
-    }
-
-    @Test
-    fun testShowNotification_stickyForSomeTime_autoDismissesWithAccessibilityTimeout() {
-        val hum = createHeadsUpManager()
-        val entry = createStickyForSomeTimeEntry(/* id= */ 0)
-        useAccessibilityTimeout(true)
-
-        hum.showNotification(entry)
-        systemClock.advanceTime(
-            (TEST_TOUCH_ACCEPTANCE_TIME +
-                    (TEST_STICKY_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2)
-                .toLong()
-        )
-
-        assertThat(hum.isHeadsUpEntry(entry.key)).isTrue()
-    }
-
-    @Test
-    fun testRemoveNotification_beforeMinimumDisplayTime() {
-        val hum = createHeadsUpManager()
-        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
-        useAccessibilityTimeout(false)
-
-        hum.showNotification(entry)
-
-        val removedImmediately =
-            hum.removeNotification(
-                entry.key,
-                /* releaseImmediately = */ false,
-                "beforeMinimumDisplayTime",
-            )
-        assertThat(removedImmediately).isFalse()
-        assertThat(hum.isHeadsUpEntry(entry.key)).isTrue()
-
-        systemClock.advanceTime(((TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2).toLong())
-
-        assertThat(hum.isHeadsUpEntry(entry.key)).isFalse()
-    }
-
-    @Test
-    fun testRemoveNotification_afterMinimumDisplayTime() {
-        val hum = createHeadsUpManager()
-        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
-        useAccessibilityTimeout(false)
-
-        hum.showNotification(entry)
-        systemClock.advanceTime(((TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2).toLong())
-
-        assertThat(hum.isHeadsUpEntry(entry.key)).isTrue()
-
-        val removedImmediately =
-            hum.removeNotification(
-                entry.key,
-                /* releaseImmediately = */ false,
-                "afterMinimumDisplayTime",
-            )
-        assertThat(removedImmediately).isTrue()
-        assertThat(hum.isHeadsUpEntry(entry.key)).isFalse()
-    }
-
-    @Test
-    fun testRemoveNotification_releaseImmediately() {
-        val hum = createHeadsUpManager()
-        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
-
-        hum.showNotification(entry)
-
-        val removedImmediately =
-            hum.removeNotification(
-                entry.key,
-                /* releaseImmediately = */ true,
-                "afterMinimumDisplayTime",
-            )
-        assertThat(removedImmediately).isTrue()
-        assertThat(hum.isHeadsUpEntry(entry.key)).isFalse()
-    }
-
-    @Test
-    fun testIsSticky_rowPinnedAndExpanded_true() {
-        val hum = createHeadsUpManager()
-        val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
-        Mockito.`when`(mRow!!.isPinned).thenReturn(true)
-        notifEntry.row = mRow
-
-        hum.showNotification(notifEntry)
-
-        val headsUpEntry = hum.getHeadsUpEntry(notifEntry.key)
-        headsUpEntry!!.setExpanded(true)
-
-        assertThat(hum.isSticky(notifEntry.key)).isTrue()
-    }
-
-    @Test
-    fun testIsSticky_remoteInputActive_true() {
-        val hum = createHeadsUpManager()
-        val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
-
-        hum.showNotification(notifEntry)
-
-        val headsUpEntry = hum.getHeadsUpEntry(notifEntry.key)
-        headsUpEntry!!.mRemoteInputActive = true
-
-        assertThat(hum.isSticky(notifEntry.key)).isTrue()
-    }
-
-    @Test
-    fun testIsSticky_hasFullScreenIntent_true() {
-        val hum = createHeadsUpManager()
-        val notifEntry = HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id= */ 0, mContext)
-
-        hum.showNotification(notifEntry)
-
-        assertThat(hum.isSticky(notifEntry.key)).isTrue()
-    }
-
-    @Test
-    fun testIsSticky_stickyForSomeTime_false() {
-        val hum = createHeadsUpManager()
-        val entry = createStickyForSomeTimeEntry(/* id= */ 0)
-
-        hum.showNotification(entry)
-
-        assertThat(hum.isSticky(entry.key)).isFalse()
-    }
-
-    @Test
-    fun testIsSticky_false() {
-        val hum = createHeadsUpManager()
-        val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
-
-        hum.showNotification(notifEntry)
-
-        val headsUpEntry = hum.getHeadsUpEntry(notifEntry.key)
-        headsUpEntry!!.setExpanded(false)
-        headsUpEntry.mRemoteInputActive = false
-
-        assertThat(hum.isSticky(notifEntry.key)).isFalse()
-    }
-
-    @Test
-    fun testCompareTo_withNullEntries() {
-        val hum = createHeadsUpManager()
-        val alertEntry = NotificationEntryBuilder().setTag("alert").build()
-
-        hum.showNotification(alertEntry)
-
-        assertThat(hum.compare(alertEntry, null)).isLessThan(0)
-        assertThat(hum.compare(null, alertEntry)).isGreaterThan(0)
-        assertThat(hum.compare(null, null)).isEqualTo(0)
-    }
-
-    @Test
-    fun testCompareTo_withNonAlertEntries() {
-        val hum = createHeadsUpManager()
-
-        val nonAlertEntry1 = NotificationEntryBuilder().setTag("nae1").build()
-        val nonAlertEntry2 = NotificationEntryBuilder().setTag("nae2").build()
-        val alertEntry = NotificationEntryBuilder().setTag("alert").build()
-        hum.showNotification(alertEntry)
-
-        assertThat(hum.compare(alertEntry, nonAlertEntry1)).isLessThan(0)
-        assertThat(hum.compare(nonAlertEntry1, alertEntry)).isGreaterThan(0)
-        assertThat(hum.compare(nonAlertEntry1, nonAlertEntry2)).isEqualTo(0)
-    }
-
-    @Test
-    fun testAlertEntryCompareTo_ongoingCallLessThanActiveRemoteInput() {
-        val hum = createHeadsUpManager()
-
-        val ongoingCall =
-            hum.HeadsUpEntry(
-                NotificationEntryBuilder()
-                    .setSbn(
-                        HeadsUpManagerTestUtil.createSbn(
-                            /* id = */ 0,
-                            Notification.Builder(mContext, "")
-                                .setCategory(Notification.CATEGORY_CALL)
-                                .setOngoing(true),
-                        )
-                    )
-                    .build()
-            )
-
-        val activeRemoteInput =
-            hum.HeadsUpEntry(HeadsUpManagerTestUtil.createEntry(/* id= */ 1, mContext))
-        activeRemoteInput.mRemoteInputActive = true
-
-        assertThat(ongoingCall.compareTo(activeRemoteInput)).isLessThan(0)
-        assertThat(activeRemoteInput.compareTo(ongoingCall)).isGreaterThan(0)
-    }
-
-    @Test
-    fun testAlertEntryCompareTo_incomingCallLessThanActiveRemoteInput() {
-        val hum = createHeadsUpManager()
-
-        val person = Person.Builder().setName("person").build()
-        val intent = Mockito.mock(PendingIntent::class.java)
-        val incomingCall =
-            hum.HeadsUpEntry(
-                NotificationEntryBuilder()
-                    .setSbn(
-                        HeadsUpManagerTestUtil.createSbn(
-                            /* id = */ 0,
-                            Notification.Builder(mContext, "")
-                                .setStyle(
-                                    Notification.CallStyle.forIncomingCall(person, intent, intent)
-                                ),
-                        )
-                    )
-                    .build()
-            )
-
-        val activeRemoteInput =
-            hum.HeadsUpEntry(HeadsUpManagerTestUtil.createEntry(/* id= */ 1, mContext))
-        activeRemoteInput.mRemoteInputActive = true
-
-        assertThat(incomingCall.compareTo(activeRemoteInput)).isLessThan(0)
-        assertThat(activeRemoteInput.compareTo(incomingCall)).isGreaterThan(0)
-    }
-
-    @Test
-    @EnableFlags(NotificationThrottleHun.FLAG_NAME)
-    fun testPinEntry_logsPeek_throttleEnabled() {
-        val hum = createHeadsUpManager()
-
-        // Needs full screen intent in order to be pinned
-        val entryToPin =
-            hum.HeadsUpEntry(
-                HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id= */ 0, mContext)
-            )
-
-        // Note: the standard way to show a notification would be calling showNotification rather
-        // than onAlertEntryAdded. However, in practice showNotification in effect adds
-        // the notification and then updates it; in order to not log twice, the entry needs
-        // to have a functional ExpandableNotificationRow that can keep track of whether it's
-        // pinned or not (via isRowPinned()). That feels like a lot to pull in to test this one bit.
-        hum.onEntryAdded(entryToPin)
-
-        assertThat(mUiEventLoggerFake.numLogs()).isEqualTo(2)
-        assertThat(AvalancheController.ThrottleEvent.AVALANCHE_THROTTLING_HUN_SHOWN.getId())
-            .isEqualTo(mUiEventLoggerFake.eventId(0))
-        assertThat(HeadsUpManagerImpl.NotificationPeekEvent.NOTIFICATION_PEEK.id)
-            .isEqualTo(mUiEventLoggerFake.eventId(1))
-    }
-
-    @Test
-    @DisableFlags(NotificationThrottleHun.FLAG_NAME)
-    fun testPinEntry_logsPeek_throttleDisabled() {
-        val hum = createHeadsUpManager()
-
-        // Needs full screen intent in order to be pinned
-        val entryToPin =
-            hum.HeadsUpEntry(
-                HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id= */ 0, mContext)
-            )
-
-        // Note: the standard way to show a notification would be calling showNotification rather
-        // than onAlertEntryAdded. However, in practice showNotification in effect adds
-        // the notification and then updates it; in order to not log twice, the entry needs
-        // to have a functional ExpandableNotificationRow that can keep track of whether it's
-        // pinned or not (via isRowPinned()). That feels like a lot to pull in to test this one bit.
-        hum.onEntryAdded(entryToPin)
-
-        assertThat(mUiEventLoggerFake.numLogs()).isEqualTo(1)
-        assertThat(HeadsUpManagerImpl.NotificationPeekEvent.NOTIFICATION_PEEK.id)
-            .isEqualTo(mUiEventLoggerFake.eventId(0))
-    }
-
-    @Test
-    fun testSetUserActionMayIndirectlyRemove() {
-        val hum = createHeadsUpManager()
-        val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
-
-        hum.showNotification(notifEntry)
-
-        assertThat(hum.canRemoveImmediately(notifEntry.key)).isFalse()
-
-        hum.setUserActionMayIndirectlyRemove(notifEntry)
-
-        assertThat(hum.canRemoveImmediately(notifEntry.key)).isTrue()
-    }
-
-    companion object {
-        const val TEST_TOUCH_ACCEPTANCE_TIME: Int = 200
-        const val TEST_A11Y_AUTO_DISMISS_TIME: Int = 1000
-        const val TEST_EXTENSION_TIME = 500
-
-        const val TEST_MINIMUM_DISPLAY_TIME: Int = 400
-        const val TEST_AUTO_DISMISS_TIME: Int = 600
-        const val TEST_STICKY_AUTO_DISMISS_TIME: Int = 800
-
-        // Number of notifications to use in tests requiring multiple notifications
-        private const val TEST_NUM_NOTIFICATIONS = 4
-
-        init {
-            Truth.assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME)
-            Truth.assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME)
-            Truth.assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_A11Y_AUTO_DISMISS_TIME)
-        }
-
-        @get:Parameters(name = "{0}")
-        @JvmStatic
-        val flags: List<FlagsParameterization>
-            get() = FlagsParameterization.allCombinationsOf(NotificationThrottleHun.FLAG_NAME)
-    }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt
index a5fecb8..8420c49 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt
@@ -15,25 +15,38 @@
  */
 package com.android.systemui.statusbar.notification.headsup
 
+import android.app.Notification
+import android.app.PendingIntent
+import android.app.Person
 import android.os.Handler
+import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.FlagsParameterization
+import android.testing.TestableLooper
 import android.testing.TestableLooper.RunWithLooper
+import android.view.accessibility.accessibilityManager
 import android.view.accessibility.accessibilityManagerWrapper
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.uiEventLoggerFake
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.concurrency.fakeExecutor
 import com.android.systemui.dump.dumpManager
+import com.android.systemui.flags.BrokenWithSceneContainer
 import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.kosmos.runTest
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.kosmos.useUnconfinedTestDispatcher
-import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.res.R
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
 import com.android.systemui.statusbar.notification.collection.provider.visualStabilityProvider
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManagerImpl.HeadsUpEntry
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper
 import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
 import com.android.systemui.statusbar.phone.keyguardBypassController
 import com.android.systemui.statusbar.policy.configurationController
@@ -41,12 +54,19 @@
 import com.android.systemui.testKosmos
 import com.android.systemui.util.concurrency.mockExecutorHandler
 import com.android.systemui.util.kotlin.JavaAdapter
+import com.android.systemui.util.settings.fakeGlobalSettings
+import com.android.systemui.util.time.fakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
 import platform.test.runner.parameterized.ParameterizedAndroidJunit4
 import platform.test.runner.parameterized.Parameters
@@ -54,19 +74,26 @@
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4::class)
 @RunWithLooper
-class HeadsUpManagerImplTest(flags: FlagsParameterization) : HeadsUpManagerImplOldTest(flags) {
-
-    private val headsUpManagerLogger = HeadsUpManagerLogger(logcatLogBuffer())
+class HeadsUpManagerImplTest(flags: FlagsParameterization) : SysuiTestCase() {
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
 
     private val kosmos = testKosmos().useUnconfinedTestDispatcher()
     private val testScope = kosmos.testScope
 
     private val groupManager = mock<GroupMembershipManager>()
     private val bgHandler = mock<Handler>()
+    private val headsUpManagerLogger = mock<HeadsUpManagerLogger>()
 
     val statusBarStateController = kosmos.sysuiStatusBarStateController
+    private val globalSettings = kosmos.fakeGlobalSettings
+    private val systemClock = kosmos.fakeSystemClock
+    private val executor = kosmos.fakeExecutor
+    private val uiEventLoggerFake = kosmos.uiEventLoggerFake
     private val javaAdapter: JavaAdapter = JavaAdapter(testScope.backgroundScope)
 
+    private lateinit var testHelper: NotificationTestHelper
     private lateinit var avalancheController: AvalancheController
     private lateinit var underTest: HeadsUpManagerImpl
 
@@ -90,12 +117,15 @@
             )
         }
 
+        allowTestableLooperAsMainThread()
+        testHelper = NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
+
         whenever(kosmos.keyguardBypassController.bypassEnabled).thenReturn(false)
         kosmos.visualStabilityProvider.isReorderingAllowed = true
         avalancheController =
             AvalancheController(
                 kosmos.dumpManager,
-                kosmos.uiEventLoggerFake,
+                uiEventLoggerFake,
                 headsUpManagerLogger,
                 bgHandler,
             )
@@ -113,7 +143,7 @@
                 systemClock,
                 executor,
                 kosmos.accessibilityManagerWrapper,
-                kosmos.uiEventLoggerFake,
+                uiEventLoggerFake,
                 javaAdapter,
                 kosmos.shadeInteractor,
                 avalancheController,
@@ -121,6 +151,220 @@
     }
 
     @Test
+    fun testHasNotifications_headsUpManagerMapNotEmpty_true() {
+        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+        underTest.showNotification(entry)
+
+        assertThat(underTest.mHeadsUpEntryMap).isNotEmpty()
+        assertThat(underTest.hasNotifications()).isTrue()
+    }
+
+    @Test
+    @EnableFlags(NotificationThrottleHun.FLAG_NAME)
+    fun testHasNotifications_avalancheMapNotEmpty_true() {
+        val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+        val headsUpEntry = underTest.createHeadsUpEntry(notifEntry)
+        avalancheController.addToNext(headsUpEntry) {}
+
+        assertThat(avalancheController.getWaitingEntryList()).isNotEmpty()
+        assertThat(underTest.hasNotifications()).isTrue()
+    }
+
+    @Test
+    @EnableFlags(NotificationThrottleHun.FLAG_NAME)
+    fun testHasNotifications_false() {
+        assertThat(underTest.mHeadsUpEntryMap).isEmpty()
+        assertThat(avalancheController.getWaitingEntryList()).isEmpty()
+        assertThat(underTest.hasNotifications()).isFalse()
+    }
+
+    @Test
+    @EnableFlags(NotificationThrottleHun.FLAG_NAME)
+    fun testGetHeadsUpEntryList_includesAvalancheEntryList() {
+        val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+        val headsUpEntry = underTest.createHeadsUpEntry(notifEntry)
+        avalancheController.addToNext(headsUpEntry) {}
+
+        assertThat(underTest.headsUpEntryList).contains(headsUpEntry)
+    }
+
+    @Test
+    @EnableFlags(NotificationThrottleHun.FLAG_NAME)
+    fun testGetHeadsUpEntry_returnsAvalancheEntry() {
+        val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+        val headsUpEntry = underTest.createHeadsUpEntry(notifEntry)
+        avalancheController.addToNext(headsUpEntry) {}
+
+        assertThat(underTest.getHeadsUpEntry(notifEntry.key)).isEqualTo(headsUpEntry)
+    }
+
+    @Test
+    fun testShowNotification_addsEntry() {
+        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+
+        underTest.showNotification(entry)
+
+        assertThat(underTest.isHeadsUpEntry(entry.key)).isTrue()
+        assertThat(underTest.hasNotifications()).isTrue()
+        assertThat(underTest.getEntry(entry.key)).isEqualTo(entry)
+    }
+
+    @Test
+    fun testShowNotification_autoDismisses() {
+        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+
+        underTest.showNotification(entry)
+        systemClock.advanceTime((TEST_AUTO_DISMISS_TIME * 3 / 2).toLong())
+
+        assertThat(underTest.isHeadsUpEntry(entry.key)).isFalse()
+    }
+
+    @Test
+    fun testRemoveNotification_removeDeferred() {
+        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+
+        underTest.showNotification(entry)
+
+        val removedImmediately =
+            underTest.removeNotification(
+                entry.key,
+                /* releaseImmediately= */ false,
+                "removeDeferred",
+            )
+        assertThat(removedImmediately).isFalse()
+        assertThat(underTest.isHeadsUpEntry(entry.key)).isTrue()
+    }
+
+    @Test
+    fun testRemoveNotification_forceRemove() {
+        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+
+        underTest.showNotification(entry)
+
+        val removedImmediately =
+            underTest.removeNotification(entry.key, /* releaseImmediately= */ true, "forceRemove")
+        assertThat(removedImmediately).isTrue()
+        assertThat(underTest.isHeadsUpEntry(entry.key)).isFalse()
+    }
+
+    @Test
+    fun testReleaseAllImmediately() {
+        for (i in 0 until 4) {
+            val entry = HeadsUpManagerTestUtil.createEntry(i, mContext)
+            entry.row = mock<ExpandableNotificationRow>()
+            underTest.showNotification(entry)
+        }
+
+        underTest.releaseAllImmediately()
+
+        assertThat(underTest.allEntries.count()).isEqualTo(0)
+    }
+
+    @Test
+    fun testCanRemoveImmediately_notShownLongEnough() {
+        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+
+        underTest.showNotification(entry)
+
+        // The entry has just been added so we should not remove immediately.
+        assertThat(underTest.canRemoveImmediately(entry.key)).isFalse()
+    }
+
+    @Test
+    fun testHunRemovedLogging() {
+        val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+        val headsUpEntry = underTest.HeadsUpEntry(notifEntry)
+        headsUpEntry.setRowPinnedStatus(PinnedStatus.NotPinned)
+
+        underTest.onEntryRemoved(headsUpEntry, "test")
+
+        verify(headsUpManagerLogger, times(1)).logNotificationActuallyRemoved(eq(notifEntry))
+    }
+
+    @Test
+    fun testShowNotification_autoDismissesIncludingTouchAcceptanceDelay() {
+        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+        useAccessibilityTimeout(false)
+
+        underTest.showNotification(entry)
+        systemClock.advanceTime((TEST_TOUCH_ACCEPTANCE_TIME / 2 + TEST_AUTO_DISMISS_TIME).toLong())
+
+        assertThat(underTest.isHeadsUpEntry(entry.key)).isTrue()
+    }
+
+    @Test
+    fun testShowNotification_autoDismissesWithDefaultTimeout() {
+        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+        useAccessibilityTimeout(false)
+
+        underTest.showNotification(entry)
+        systemClock.advanceTime(
+            (TEST_TOUCH_ACCEPTANCE_TIME +
+                    (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2)
+                .toLong()
+        )
+
+        assertThat(underTest.isHeadsUpEntry(entry.key)).isFalse()
+    }
+
+    @Test
+    fun testRemoveNotification_beforeMinimumDisplayTime() {
+        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+        useAccessibilityTimeout(false)
+
+        underTest.showNotification(entry)
+
+        val removedImmediately =
+            underTest.removeNotification(
+                entry.key,
+                /* releaseImmediately = */ false,
+                "beforeMinimumDisplayTime",
+            )
+        assertThat(removedImmediately).isFalse()
+        assertThat(underTest.isHeadsUpEntry(entry.key)).isTrue()
+
+        systemClock.advanceTime(((TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2).toLong())
+
+        assertThat(underTest.isHeadsUpEntry(entry.key)).isFalse()
+    }
+
+    @Test
+    fun testRemoveNotification_afterMinimumDisplayTime() {
+        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+        useAccessibilityTimeout(false)
+
+        underTest.showNotification(entry)
+        systemClock.advanceTime(((TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2).toLong())
+
+        assertThat(underTest.isHeadsUpEntry(entry.key)).isTrue()
+
+        val removedImmediately =
+            underTest.removeNotification(
+                entry.key,
+                /* releaseImmediately = */ false,
+                "afterMinimumDisplayTime",
+            )
+        assertThat(removedImmediately).isTrue()
+        assertThat(underTest.isHeadsUpEntry(entry.key)).isFalse()
+    }
+
+    @Test
+    fun testRemoveNotification_releaseImmediately() {
+        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+
+        underTest.showNotification(entry)
+
+        val removedImmediately =
+            underTest.removeNotification(
+                entry.key,
+                /* releaseImmediately = */ true,
+                "afterMinimumDisplayTime",
+            )
+        assertThat(removedImmediately).isTrue()
+        assertThat(underTest.isHeadsUpEntry(entry.key)).isFalse()
+    }
+
+    @Test
     fun testSnooze() {
         val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
         underTest.showNotification(entry)
@@ -160,7 +404,7 @@
     fun testCanRemoveImmediately_notTopEntry() {
         val earlierEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
         val laterEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 1, mContext)
-        laterEntry.row = mRow
+        laterEntry.row = mock<ExpandableNotificationRow>()
         underTest.showNotification(earlierEntry)
         underTest.showNotification(laterEntry)
 
@@ -226,6 +470,122 @@
     }
 
     @Test
+    fun testShowNotification_sticky_neverAutoDismisses() {
+        val entry = createStickyEntry(id = 0)
+        useAccessibilityTimeout(false)
+
+        underTest.showNotification(entry)
+        systemClock.advanceTime(
+            (TEST_TOUCH_ACCEPTANCE_TIME + 2 * TEST_A11Y_AUTO_DISMISS_TIME).toLong()
+        )
+
+        assertThat(underTest.isHeadsUpEntry(entry.key)).isTrue()
+    }
+
+    @Test
+    fun testShowNotification_autoDismissesWithAccessibilityTimeout() {
+        val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+        useAccessibilityTimeout(true)
+
+        underTest.showNotification(entry)
+        systemClock.advanceTime(
+            (TEST_TOUCH_ACCEPTANCE_TIME +
+                    (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2)
+                .toLong()
+        )
+
+        assertThat(underTest.isHeadsUpEntry(entry.key)).isTrue()
+    }
+
+    @Test
+    fun testShowNotification_stickyForSomeTime_autoDismissesWithStickyTimeout() {
+        val entry = createStickyForSomeTimeEntry(id = 0)
+        useAccessibilityTimeout(false)
+
+        underTest.showNotification(entry)
+        systemClock.advanceTime(
+            (TEST_TOUCH_ACCEPTANCE_TIME +
+                    (TEST_AUTO_DISMISS_TIME + TEST_STICKY_AUTO_DISMISS_TIME) / 2)
+                .toLong()
+        )
+
+        assertThat(underTest.isHeadsUpEntry(entry.key)).isTrue()
+    }
+
+    @Test
+    fun testShowNotification_stickyForSomeTime_autoDismissesWithAccessibilityTimeout() {
+        val entry = createStickyForSomeTimeEntry(id = 0)
+        useAccessibilityTimeout(true)
+
+        underTest.showNotification(entry)
+        systemClock.advanceTime(
+            (TEST_TOUCH_ACCEPTANCE_TIME +
+                    (TEST_STICKY_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2)
+                .toLong()
+        )
+
+        assertThat(underTest.isHeadsUpEntry(entry.key)).isTrue()
+    }
+
+    @Test
+    fun testIsSticky_rowPinnedAndExpanded_true() {
+        val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+        val row = testHelper.createRow()
+        row.setPinnedStatus(PinnedStatus.PinnedBySystem)
+        notifEntry.row = row
+
+        underTest.showNotification(notifEntry)
+
+        val headsUpEntry = underTest.getHeadsUpEntry(notifEntry.key)
+        headsUpEntry!!.setExpanded(true)
+
+        assertThat(underTest.isSticky(notifEntry.key)).isTrue()
+    }
+
+    @Test
+    fun testIsSticky_remoteInputActive_true() {
+        val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+
+        underTest.showNotification(notifEntry)
+
+        val headsUpEntry = underTest.getHeadsUpEntry(notifEntry.key)
+        headsUpEntry!!.mRemoteInputActive = true
+
+        assertThat(underTest.isSticky(notifEntry.key)).isTrue()
+    }
+
+    @Test
+    fun testIsSticky_hasFullScreenIntent_true() {
+        val notifEntry = HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id= */ 0, mContext)
+
+        underTest.showNotification(notifEntry)
+
+        assertThat(underTest.isSticky(notifEntry.key)).isTrue()
+    }
+
+    @Test
+    fun testIsSticky_stickyForSomeTime_false() {
+        val entry = createStickyForSomeTimeEntry(id = 0)
+
+        underTest.showNotification(entry)
+
+        assertThat(underTest.isSticky(entry.key)).isFalse()
+    }
+
+    @Test
+    fun testIsSticky_false() {
+        val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+
+        underTest.showNotification(notifEntry)
+
+        val headsUpEntry = underTest.getHeadsUpEntry(notifEntry.key)
+        headsUpEntry!!.setExpanded(false)
+        headsUpEntry.mRemoteInputActive = false
+
+        assertThat(underTest.isSticky(notifEntry.key)).isFalse()
+    }
+
+    @Test
     fun testShouldHeadsUpBecomePinned_noFSI_false() =
         kosmos.runTest {
             statusBarStateController.setState(StatusBarState.KEYGUARD)
@@ -270,11 +630,13 @@
         }
 
     @Test
+    @BrokenWithSceneContainer(381869885) // because `ShadeTestUtil.setShadeExpansion(0f)`
+    // still causes `ShadeInteractor.isAnyExpanded` to emit `true`, when it should emit `false`.
     fun shouldHeadsUpBecomePinned_shadeNotExpanded_true() =
         kosmos.runTest {
             // GIVEN
-            shadeTestUtil.setShadeExpansion(0f)
-            // TODO(b/381869885): Determine why we need both of these ShadeTestUtil calls.
+            // TODO(b/381869885): We should be able to use `ShadeTestUtil.setShadeExpansion(0f)`
+            // instead.
             shadeTestUtil.setLegacyExpandedOrAwaitingInputTransfer(false)
 
             val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
@@ -347,8 +709,183 @@
             assertThat(underTest.shouldHeadsUpBecomePinned(entry)).isFalse()
         }
 
+    @Test
+    fun testCompareTo_withNullEntries() {
+        val alertEntry = NotificationEntryBuilder().setTag("alert").build()
+
+        underTest.showNotification(alertEntry)
+
+        assertThat(underTest.compare(alertEntry, null)).isLessThan(0)
+        assertThat(underTest.compare(null, alertEntry)).isGreaterThan(0)
+        assertThat(underTest.compare(null, null)).isEqualTo(0)
+    }
+
+    @Test
+    fun testCompareTo_withNonAlertEntries() {
+        val nonAlertEntry1 = NotificationEntryBuilder().setTag("nae1").build()
+        val nonAlertEntry2 = NotificationEntryBuilder().setTag("nae2").build()
+        val alertEntry = NotificationEntryBuilder().setTag("alert").build()
+        underTest.showNotification(alertEntry)
+
+        assertThat(underTest.compare(alertEntry, nonAlertEntry1)).isLessThan(0)
+        assertThat(underTest.compare(nonAlertEntry1, alertEntry)).isGreaterThan(0)
+        assertThat(underTest.compare(nonAlertEntry1, nonAlertEntry2)).isEqualTo(0)
+    }
+
+    @Test
+    fun testAlertEntryCompareTo_ongoingCallLessThanActiveRemoteInput() {
+        val ongoingCall =
+            underTest.HeadsUpEntry(
+                NotificationEntryBuilder()
+                    .setSbn(
+                        HeadsUpManagerTestUtil.createSbn(
+                            /* id = */ 0,
+                            Notification.Builder(mContext, "")
+                                .setCategory(Notification.CATEGORY_CALL)
+                                .setOngoing(true),
+                        )
+                    )
+                    .build()
+            )
+
+        val activeRemoteInput =
+            underTest.HeadsUpEntry(HeadsUpManagerTestUtil.createEntry(/* id= */ 1, mContext))
+        activeRemoteInput.mRemoteInputActive = true
+
+        assertThat(ongoingCall.compareTo(activeRemoteInput)).isLessThan(0)
+        assertThat(activeRemoteInput.compareTo(ongoingCall)).isGreaterThan(0)
+    }
+
+    @Test
+    fun testAlertEntryCompareTo_incomingCallLessThanActiveRemoteInput() {
+        val person = Person.Builder().setName("person").build()
+        val intent = mock<PendingIntent>()
+        val incomingCall =
+            underTest.HeadsUpEntry(
+                NotificationEntryBuilder()
+                    .setSbn(
+                        HeadsUpManagerTestUtil.createSbn(
+                            /* id = */ 0,
+                            Notification.Builder(mContext, "")
+                                .setStyle(
+                                    Notification.CallStyle.forIncomingCall(person, intent, intent)
+                                ),
+                        )
+                    )
+                    .build()
+            )
+
+        val activeRemoteInput =
+            underTest.HeadsUpEntry(HeadsUpManagerTestUtil.createEntry(/* id= */ 1, mContext))
+        activeRemoteInput.mRemoteInputActive = true
+
+        assertThat(incomingCall.compareTo(activeRemoteInput)).isLessThan(0)
+        assertThat(activeRemoteInput.compareTo(incomingCall)).isGreaterThan(0)
+    }
+
+    @Test
+    @EnableFlags(NotificationThrottleHun.FLAG_NAME)
+    fun testPinEntry_logsPeek_throttleEnabled() {
+        // Needs full screen intent in order to be pinned
+        val entryToPin =
+            underTest.HeadsUpEntry(
+                HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id= */ 0, mContext)
+            )
+
+        // Note: the standard way to show a notification would be calling showNotification rather
+        // than onAlertEntryAdded. However, in practice showNotification in effect adds
+        // the notification and then updates it; in order to not log twice, the entry needs
+        // to have a functional ExpandableNotificationRow that can keep track of whether it's
+        // pinned or not (via isRowPinned()). That feels like a lot to pull in to test this one bit.
+        underTest.onEntryAdded(entryToPin)
+
+        assertThat(uiEventLoggerFake.numLogs()).isEqualTo(2)
+        assertThat(AvalancheController.ThrottleEvent.AVALANCHE_THROTTLING_HUN_SHOWN.getId())
+            .isEqualTo(uiEventLoggerFake.eventId(0))
+        assertThat(HeadsUpManagerImpl.NotificationPeekEvent.NOTIFICATION_PEEK.id)
+            .isEqualTo(uiEventLoggerFake.eventId(1))
+    }
+
+    @Test
+    @DisableFlags(NotificationThrottleHun.FLAG_NAME)
+    fun testPinEntry_logsPeek_throttleDisabled() {
+        // Needs full screen intent in order to be pinned
+        val entryToPin =
+            underTest.HeadsUpEntry(
+                HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id= */ 0, mContext)
+            )
+
+        // Note: the standard way to show a notification would be calling showNotification rather
+        // than onAlertEntryAdded. However, in practice showNotification in effect adds
+        // the notification and then updates it; in order to not log twice, the entry needs
+        // to have a functional ExpandableNotificationRow that can keep track of whether it's
+        // pinned or not (via isRowPinned()). That feels like a lot to pull in to test this one bit.
+        underTest.onEntryAdded(entryToPin)
+
+        assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+        assertThat(HeadsUpManagerImpl.NotificationPeekEvent.NOTIFICATION_PEEK.id)
+            .isEqualTo(uiEventLoggerFake.eventId(0))
+    }
+
+    @Test
+    fun testSetUserActionMayIndirectlyRemove() {
+        val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+
+        underTest.showNotification(notifEntry)
+
+        assertThat(underTest.canRemoveImmediately(notifEntry.key)).isFalse()
+
+        underTest.setUserActionMayIndirectlyRemove(notifEntry)
+
+        assertThat(underTest.canRemoveImmediately(notifEntry.key)).isTrue()
+    }
+
+    private fun createStickyEntry(id: Int): NotificationEntry {
+        val notif =
+            Notification.Builder(mContext, "")
+                .setSmallIcon(R.drawable.ic_person)
+                .setFullScreenIntent(mock<PendingIntent>(), /* highPriority= */ true)
+                .build()
+        return HeadsUpManagerTestUtil.createEntry(id, notif)
+    }
+
+    private fun createStickyForSomeTimeEntry(id: Int): NotificationEntry {
+        val notif =
+            Notification.Builder(mContext, "")
+                .setSmallIcon(R.drawable.ic_person)
+                .setFlag(Notification.FLAG_FSI_REQUESTED_BUT_DENIED, true)
+                .build()
+        return HeadsUpManagerTestUtil.createEntry(id, notif)
+    }
+
+    private fun useAccessibilityTimeout(use: Boolean) {
+        if (use) {
+            whenever(kosmos.accessibilityManager.getRecommendedTimeoutMillis(any(), any()))
+                .thenReturn(TEST_A11Y_AUTO_DISMISS_TIME)
+        } else {
+            doAnswer { it.getArgument(0) as Int }
+                .whenever(kosmos.accessibilityManager)
+                .getRecommendedTimeoutMillis(any(), any())
+        }
+    }
+
     companion object {
+        const val TEST_TOUCH_ACCEPTANCE_TIME = 200
+        const val TEST_A11Y_AUTO_DISMISS_TIME = 1000
+        const val TEST_EXTENSION_TIME = 500
+
+        const val TEST_MINIMUM_DISPLAY_TIME = 400
+        const val TEST_AUTO_DISMISS_TIME = 600
+        const val TEST_STICKY_AUTO_DISMISS_TIME = 800
+
+        init {
+            assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME)
+            assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME)
+            assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_A11Y_AUTO_DISMISS_TIME)
+        }
+
         @get:Parameters(name = "{0}")
+        @JvmStatic
         val flags: List<FlagsParameterization>
             get() = buildList {
                 addAll(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewBinder.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewBinder.kt
index 9888574..a2ca12c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewBinder.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewBinder.kt
@@ -30,6 +30,7 @@
     var listener: StatusBarVisibilityChangeListener? = null
 
     override fun bind(
+        displayId: Int,
         view: View,
         viewModel: HomeStatusBarViewModel,
         systemEventChipAnimateIn: ((View) -> Unit)?,
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarView.java b/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarView.java
index d4e74d3..9e1b09c 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarView.java
@@ -162,11 +162,12 @@
 
     void showIcon(@StatusIconType int iconType, boolean show, @Nullable String contentDescription) {
         View icon = mStatusIcons.get(iconType);
-        if (icon == null) {
-            return;
-        }
+        if (icon == null) return;
+
         if (show && contentDescription != null) {
             icon.setContentDescription(contentDescription);
+            icon.setFocusable(true);
+            icon.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
         }
         icon.setVisibility(show ? View.VISIBLE : View.GONE);
         mSystemStatusViewGroup.setVisibility(areAnyStatusIconsVisible() ? View.VISIBLE : View.GONE);
@@ -174,9 +175,12 @@
 
     void setExtraStatusBarItemViews(List<View> views) {
         removeAllExtraStatusBarItemViews();
-        views.forEach(view -> mExtraSystemStatusViewGroup.addView(view));
+        views.forEach(view -> {
+            view.setFocusable(true);
+            view.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+            mExtraSystemStatusViewGroup.addView(view);
+        });
     }
-
     private View fetchStatusIconForResId(int resId) {
         final View statusIcon = findViewById(resId);
         return Objects.requireNonNull(statusIcon);
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/domain/interactor/FalsingInteractor.kt b/packages/SystemUI/src/com/android/systemui/classifier/domain/interactor/FalsingInteractor.kt
index 074b64e..69f4f6d 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/domain/interactor/FalsingInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/classifier/domain/interactor/FalsingInteractor.kt
@@ -74,3 +74,12 @@
     /** Returns `true` if the tap gesture should be rejected */
     fun isFalseTap(@Penalty penalty: Int): Boolean = manager.isFalseTap(penalty)
 }
+
+inline fun FalsingInteractor.runIfNotFalseTap(
+    penalty: Int = FalsingManager.LOW_PENALTY,
+    action: () -> Unit,
+) {
+    if (!isFalseTap(penalty)) {
+        action()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index d648b9c..5644e6b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -23,7 +23,6 @@
 import com.android.compose.animation.scene.TransitionKey
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.CoreStartable
-import com.android.systemui.Flags.communalHubOnMobile
 import com.android.systemui.Flags.communalSceneKtfRefactor
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
@@ -218,7 +217,8 @@
                             newScene = CommunalScenes.Blank,
                             loggingReason = "hub timeout",
                             transitionKey =
-                                if (communalHubOnMobile()) CommunalTransitionKeys.SimpleFade
+                                if (communalSettingsInteractor.isV2FlagEnabled())
+                                    CommunalTransitionKeys.SimpleFade
                                 else null,
                         )
                         uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_TIMEOUT)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
index 0cdbad4..862b05b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
@@ -63,24 +63,41 @@
             .logDiffsForTable(
                 tableLogBuffer = tableLogBuffer,
                 columnPrefix = "disabledReason",
-                initialValue = CommunalEnabledState()
+                initialValue = CommunalEnabledState(),
             )
             .map { model -> model.enabled }
             // Start this eagerly since the value is accessed synchronously in many places.
             .stateIn(scope = bgScope, started = SharingStarted.Eagerly, initialValue = false)
 
     /**
-     * Returns true if both the communal trunk-stable flag and resource flag are enabled.
+     * Returns true if any glanceable hub functionality should be enabled via configs and flags.
      *
-     * The trunk-stable flag is controlled by server rollout and is on all devices. The resource
-     * flag is enabled via resource overlay only on products we want the hub to be present on.
+     * This should be used for preventing basic glanceable hub functionality from running on devices
+     * that don't need it.
      *
      * If this is false, then the hub is definitely not available on the device. If this is true,
      * refer to [isCommunalEnabled] which takes into account other factors that can change at
      * runtime.
+     *
+     * If the glanceable_hub_v2 flag is enabled, checks the config_glanceableHubEnabled Android
+     * config boolean. Otherwise, checks the old config_communalServiceEnabled config and
+     * communal_hub flag.
      */
     fun isCommunalFlagEnabled(): Boolean = repository.getFlagEnabled()
 
+    /**
+     * Returns true if the Android config config_glanceableHubEnabled and the glanceable_hub_v2 flag
+     * are enabled.
+     *
+     * This should be used to flag off new glanceable hub or dream behavior that should launch
+     * together with the new hub experience that brings the hub to mobile.
+     *
+     * The trunk-stable flag is controlled by server rollout and is on all devices. The Android
+     * config flag is enabled via resource overlay only on products we want the hub to be present
+     * on.
+     */
+    fun isV2FlagEnabled(): Boolean = repository.getV2FlagEnabled()
+
     /** The type of background to use for the hub. Used to experiment with different backgrounds */
     val communalBackground: Flow<CommunalBackgroundType> =
         userInteractor.selectedUserInfo
@@ -120,6 +137,6 @@
             .stateIn(
                 scope = bgScope,
                 started = SharingStarted.WhileSubscribed(),
-                initialValue = null
+                initialValue = null,
             )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 9cd6465..eb7420f 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -90,7 +90,7 @@
     private val keyguardIndicationController: KeyguardIndicationController,
     communalSceneInteractor: CommunalSceneInteractor,
     private val communalInteractor: CommunalInteractor,
-    communalSettingsInteractor: CommunalSettingsInteractor,
+    private val communalSettingsInteractor: CommunalSettingsInteractor,
     tutorialInteractor: CommunalTutorialInteractor,
     private val shadeInteractor: ShadeInteractor,
     @Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
@@ -372,6 +372,9 @@
     val communalBackground: Flow<CommunalBackgroundType> =
         communalSettingsInteractor.communalBackground
 
+    /** See [CommunalSettingsInteractor.isV2FlagEnabled] */
+    fun v2FlagEnabled(): Boolean = communalSettingsInteractor.isV2FlagEnabled()
+
     companion object {
         const val POPUP_AUTO_HIDE_TIMEOUT_MS = 12000L
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.kt
index e76fd47..c425bee 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.kt
@@ -25,7 +25,7 @@
 import android.os.RemoteException
 import android.service.dreams.IDreamManager
 import android.util.Log
-import com.android.systemui.Flags
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
 import com.android.systemui.dagger.qualifiers.SystemUser
 import com.android.systemui.dreams.dagger.DreamModule
 import com.android.systemui.log.LogBuffer
@@ -48,6 +48,7 @@
     @SystemUser monitor: Monitor,
     private val packageManager: PackageManager,
     private val dreamManager: IDreamManager,
+    private val communalSettingsInteractor: CommunalSettingsInteractor,
     @DreamLog private val logBuffer: LogBuffer,
 ) : ConditionalCoreStartable(monitor) {
     private var currentRegisteredState = false
@@ -90,7 +91,7 @@
             }
 
             if (
-                Flags.communalHubOnMobile() &&
+                communalSettingsInteractor.isV2FlagEnabled() &&
                     packageManager.getComponentEnabledSetting(overlayServiceComponent) ==
                         PackageManager.COMPONENT_ENABLED_STATE_ENABLED
             ) {
@@ -111,7 +112,7 @@
         }
 
         // Enable for hub on mobile
-        if (Flags.communalHubOnMobile()) {
+        if (communalSettingsInteractor.isV2FlagEnabled()) {
             // Not available on TV or auto
             if (
                 packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) ||
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index aee3a45..571b37f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -18,7 +18,6 @@
 
 import static android.service.dreams.Flags.dreamWakeRedirect;
 
-import static com.android.systemui.Flags.communalHubOnMobile;
 import static com.android.systemui.Flags.glanceableHubAllowKeyguardWhenDreaming;
 import static com.android.systemui.dreams.dagger.DreamModule.DREAM_OVERLAY_WINDOW_TITLE;
 import static com.android.systemui.dreams.dagger.DreamModule.DREAM_TOUCH_INSET_MANAGER;
@@ -60,6 +59,7 @@
 import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent;
 import com.android.systemui.ambient.touch.scrim.ScrimManager;
 import com.android.systemui.communal.domain.interactor.CommunalInteractor;
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor;
 import com.android.systemui.communal.shared.log.CommunalUiEvent;
 import com.android.systemui.communal.shared.model.CommunalScenes;
 import com.android.systemui.communal.shared.model.CommunalTransitionKeys;
@@ -171,6 +171,7 @@
 
     private final SceneInteractor mSceneInteractor;
     private final CommunalInteractor mCommunalInteractor;
+    private final CommunalSettingsInteractor mCommunalSettingsInteractor;
 
     private boolean mCommunalAvailable;
 
@@ -383,6 +384,7 @@
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             ScrimManager scrimManager,
             CommunalInteractor communalInteractor,
+            CommunalSettingsInteractor communalSettingsInteractor,
             SceneInteractor sceneInteractor,
             SystemDialogsCloser systemDialogsCloser,
             UiEventLogger uiEventLogger,
@@ -411,6 +413,7 @@
         mDreamOverlayCallbackController = dreamOverlayCallbackController;
         mWindowTitle = windowTitle;
         mCommunalInteractor = communalInteractor;
+        mCommunalSettingsInteractor = communalSettingsInteractor;
         mSceneInteractor = sceneInteractor;
         mSystemDialogsCloser = systemDialogsCloser;
         mGestureInteractor = gestureInteractor;
@@ -488,7 +491,7 @@
 
         final ArrayList<TouchHandler> touchHandlers = new ArrayList<>(
                 List.of(dreamComplicationComponent.getHideComplicationTouchHandler()));
-        if (!communalHubOnMobile()) {
+        if (!mCommunalSettingsInteractor.isV2FlagEnabled()) {
             // Do not add the communal touch handler for glanceable hub v2 since there is no dream
             // to hub swipe gesture.
             touchHandlers.add(dreamOverlayComponent.getCommunalTouchHandler());
@@ -575,7 +578,8 @@
         } else {
             mCommunalInteractor.changeScene(CommunalScenes.Communal,
                     "dream wake requested",
-                    communalHubOnMobile() ? CommunalTransitionKeys.INSTANCE.getSimpleFade() : null);
+                    mCommunalSettingsInteractor.isV2FlagEnabled()
+                            ? CommunalTransitionKeys.INSTANCE.getSimpleFade() : null);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt
index 71f29c0..d335a18 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt
@@ -20,12 +20,12 @@
 import android.content.Intent
 import android.provider.Settings
 import android.util.Log
-import com.android.systemui.Flags.glanceableHubShortcutButton
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.communal.data.repository.CommunalSceneRepository
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.shared.model.CommunalTransitionKeys
 import com.android.systemui.dagger.SysUISingleton
@@ -46,6 +46,7 @@
     @Application private val context: Context,
     private val communalSceneRepository: CommunalSceneRepository,
     private val communalInteractor: CommunalInteractor,
+    private val communalSettingsInteractor: CommunalSettingsInteractor,
     private val sceneInteractor: SceneInteractor,
 ) : KeyguardQuickAffordanceConfig {
 
@@ -61,8 +62,7 @@
     override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState>
         get() = flow {
             emit(
-                // TODO(b/378113263): Gate on getV2FlagEnabled() when ready.
-                if (!glanceableHubShortcutButton()) {
+                if (!communalSettingsInteractor.isV2FlagEnabled()) {
                     Log.i(TAG, "Button hidden on lockscreen: flag not enabled.")
                     KeyguardQuickAffordanceConfig.LockScreenState.Hidden
                 } else if (!communalInteractor.isCommunalEnabled.value) {
@@ -81,8 +81,7 @@
         }
 
     override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
-        // TODO(b/378113263): Gate on getV2FlagEnabled() when ready.
-        return if (!glanceableHubShortcutButton()) {
+        return if (!communalSettingsInteractor.isV2FlagEnabled()) {
             Log.i(TAG, "Button unavailable in picker: flag not enabled.")
             KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
         } else if (!communalInteractor.isCommunalEnabled.value) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 25e6f0e..a999211 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -21,7 +21,6 @@
 import android.app.DreamManager
 import com.android.app.animation.Interpolators
 import com.android.app.tracing.coroutines.launchTraced as launch
-import com.android.systemui.Flags.communalHubOnMobile
 import com.android.systemui.Flags.communalSceneKtfRefactor
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
@@ -177,7 +176,8 @@
                             newScene = CommunalScenes.Communal,
                             loggingReason = "FromDreamingTransitionInteractor",
                             transitionKey =
-                                if (communalHubOnMobile()) CommunalTransitionKeys.SimpleFade
+                                if (communalSettingsInteractor.isV2FlagEnabled())
+                                    CommunalTransitionKeys.SimpleFade
                                 else null,
                         )
                     } else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index d30a59f..8d9f49e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -47,7 +47,6 @@
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.heightIn
 import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.verticalScroll
@@ -709,12 +708,7 @@
                                     GridAnchor()
                                     TileGrid(
                                         viewModel = containerViewModel.tileGridViewModel,
-                                        modifier =
-                                            Modifier.fillMaxWidth()
-                                                .heightIn(
-                                                    max =
-                                                        QuickSettingsShade.Dimensions.GridMaxHeight
-                                                ),
+                                        modifier = Modifier.fillMaxWidth(),
                                     )
                                 }
                             }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
index 8ef6375..cc87206 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
@@ -23,12 +23,12 @@
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleCoroutineScope
 import androidx.lifecycle.LifecycleOwner
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.settingslib.Utils
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.globalactions.GlobalActionsDialogLite
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
@@ -38,6 +38,7 @@
 import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig
 import com.android.systemui.res.R
 import com.android.systemui.shade.ShadeDisplayAware
+import com.android.systemui.shade.shared.flag.DualShade
 import com.android.systemui.util.icuMessageFormat
 import javax.inject.Inject
 import javax.inject.Named
@@ -54,7 +55,6 @@
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.isActive
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 private const val TAG = "FooterActionsViewModel"
 
@@ -113,7 +113,7 @@
     class Factory
     @Inject
     constructor(
-        @ShadeDisplayAware  private val context: Context,
+        @ShadeDisplayAware private val context: Context,
         private val falsingManager: FalsingManager,
         private val footerActionsInteractor: FooterActionsInteractor,
         private val globalActionsDialogLiteProvider: Provider<GlobalActionsDialogLite>,
@@ -211,7 +211,7 @@
                 false /* if the dismiss should be deferred */
             },
             null /* cancelAction */,
-            true /* afterKeyguardGone */
+            true, /* afterKeyguardGone */
         )
     }
 
@@ -269,29 +269,7 @@
             .distinctUntilChanged()
 
     val userSwitcher =
-        footerActionsInteractor.userSwitcherStatus
-            .map { userSwitcherStatus ->
-                when (userSwitcherStatus) {
-                    UserSwitcherStatusModel.Disabled -> null
-                    is UserSwitcherStatusModel.Enabled -> {
-                        if (userSwitcherStatus.currentUserImage == null) {
-                            Log.e(
-                                TAG,
-                                "Skipped the addition of user switcher button because " +
-                                    "currentUserImage is missing",
-                            )
-                            return@map null
-                        }
-
-                        userSwitcherButtonViewModel(
-                            qsThemedContext,
-                            userSwitcherStatus,
-                            ::onUserSwitcherClicked
-                        )
-                    }
-                }
-            }
-            .distinctUntilChanged()
+        userSwitcherViewModel(qsThemedContext, footerActionsInteractor, ::onUserSwitcherClicked)
 
     val settings = settingsButtonViewModel(qsThemedContext, ::onSettingsButtonClicked)
     val power =
@@ -311,6 +289,36 @@
     )
 }
 
+fun userSwitcherViewModel(
+    themedContext: Context,
+    footerActionsInteractor: FooterActionsInteractor,
+    onUserSwitcherClicked: (Expandable) -> Unit,
+): Flow<FooterActionsButtonViewModel?> {
+    return footerActionsInteractor.userSwitcherStatus
+        .map { userSwitcherStatus ->
+            when (userSwitcherStatus) {
+                UserSwitcherStatusModel.Disabled -> null
+                is UserSwitcherStatusModel.Enabled -> {
+                    if (userSwitcherStatus.currentUserImage == null) {
+                        Log.e(
+                            TAG,
+                            "Skipped the addition of user switcher button because " +
+                                "currentUserImage is missing",
+                        )
+                        return@map null
+                    }
+
+                    userSwitcherButtonViewModel(
+                        themedContext,
+                        userSwitcherStatus,
+                        onUserSwitcherClicked,
+                    )
+                }
+            }
+        }
+        .distinctUntilChanged()
+}
+
 fun securityButtonViewModel(
     config: SecurityButtonConfig,
     onSecurityButtonClicked: (Context, Expandable) -> Unit,
@@ -369,7 +377,7 @@
 
 private fun userSwitcherContentDescription(
     qsThemedContext: Context,
-    currentUser: String?
+    currentUser: String?,
 ): String? {
     return currentUser?.let { user ->
         qsThemedContext.getString(R.string.accessibility_quick_settings_user, user)
@@ -384,13 +392,9 @@
         id = R.id.settings_button_container,
         Icon.Resource(
             R.drawable.ic_settings,
-            ContentDescription.Resource(R.string.accessibility_quick_settings_settings)
+            ContentDescription.Resource(R.string.accessibility_quick_settings_settings),
         ),
-        iconTint =
-            Utils.getColorAttrDefaultColor(
-                qsThemedContext,
-                R.attr.onShadeInactiveVariant,
-            ),
+        iconTint = Utils.getColorAttrDefaultColor(qsThemedContext, R.attr.onShadeInactiveVariant),
         backgroundColor = R.attr.shadeInactive,
         onSettingsButtonClicked,
     )
@@ -404,14 +408,14 @@
         id = R.id.pm_lite,
         Icon.Resource(
             android.R.drawable.ic_lock_power_off,
-            ContentDescription.Resource(R.string.accessibility_quick_settings_power_menu)
+            ContentDescription.Resource(R.string.accessibility_quick_settings_power_menu),
         ),
         iconTint =
             Utils.getColorAttrDefaultColor(
                 qsThemedContext,
-                R.attr.onShadeActive,
+                if (DualShade.isEnabled) R.attr.onShadeInactiveVariant else R.attr.onShadeActive,
             ),
-        backgroundColor = R.attr.shadeActive,
+        backgroundColor = if (DualShade.isEnabled) R.attr.shadeInactive else R.attr.shadeActive,
         onPowerButtonClicked,
     )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
index 1f55ac7..f4bf53ca 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt
@@ -21,8 +21,6 @@
 import com.android.systemui.log.LogBufferFactory
 import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
 import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepositoryImpl
-import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepository
-import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepositoryImpl
 import com.android.systemui.qs.panels.domain.interactor.EditTilesResetInteractor
 import com.android.systemui.qs.panels.domain.interactor.SizedTilesResetInteractor
 import com.android.systemui.qs.panels.shared.model.GridLayoutType
@@ -49,9 +47,6 @@
     ): DefaultLargeTilesRepository
 
     @Binds
-    fun bindGridLayoutTypeRepository(impl: GridLayoutTypeRepositoryImpl): GridLayoutTypeRepository
-
-    @Binds
     fun bindEditTilesResetInteractor(impl: SizedTilesResetInteractor): EditTilesResetInteractor
 
     @Binds fun bindIconTilesViewModel(impl: IconTilesViewModelImpl): IconTilesViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt
index 47c4ffd..f17abe8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt
@@ -17,28 +17,14 @@
 package com.android.systemui.qs.panels.data.repository
 
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.qs.panels.shared.model.GridLayoutType
+import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
 import com.android.systemui.qs.panels.shared.model.PaginatedGridLayoutType
 import javax.inject.Inject
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
-
-interface GridLayoutTypeRepository {
-    val layout: StateFlow<GridLayoutType>
-
-    fun setLayout(type: GridLayoutType)
-}
+import kotlinx.coroutines.flow.flowOf
 
 @SysUISingleton
-class GridLayoutTypeRepositoryImpl @Inject constructor() : GridLayoutTypeRepository {
-    private val _layout: MutableStateFlow<GridLayoutType> =
-        MutableStateFlow(PaginatedGridLayoutType)
-    override val layout = _layout.asStateFlow()
+class GridLayoutTypeRepository @Inject constructor() {
+    val defaultLayoutType = flowOf(PaginatedGridLayoutType)
 
-    override fun setLayout(type: GridLayoutType) {
-        if (_layout.value != type) {
-            _layout.value = type
-        }
-    }
+    val dualShadeLayoutType = flowOf(InfiniteGridLayoutType)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractor.kt
index 4af1b22..e493cbe 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractor.kt
@@ -19,14 +19,23 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepository
 import com.android.systemui.qs.panels.shared.model.GridLayoutType
+import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
 import javax.inject.Inject
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flatMapLatest
 
 @SysUISingleton
-class GridLayoutTypeInteractor @Inject constructor(private val repo: GridLayoutTypeRepository) {
-    val layout: StateFlow<GridLayoutType> = repo.layout
-
-    fun setLayoutType(type: GridLayoutType) {
-        repo.setLayout(type)
-    }
+@OptIn(ExperimentalCoroutinesApi::class)
+class GridLayoutTypeInteractor
+@Inject
+constructor(private val repo: GridLayoutTypeRepository, shadeModeInteractor: ShadeModeInteractor) {
+    val layout: Flow<GridLayoutType> =
+        shadeModeInteractor.shadeMode.flatMapLatest { shadeMode ->
+            when (shadeMode) {
+                is ShadeMode.Dual -> repo.dualShadeLayoutType
+                else -> repo.defaultLayoutType
+            }
+        }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
index b6dbf4d..39408d3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
@@ -49,9 +49,10 @@
 import com.android.systemui.qs.panels.dagger.PaginatedBaseLayoutType
 import com.android.systemui.qs.panels.ui.compose.Dimensions.FooterHeight
 import com.android.systemui.qs.panels.ui.compose.Dimensions.InterPageSpacing
-import com.android.systemui.qs.panels.ui.viewmodel.EditModeButtonViewModel
+import com.android.systemui.qs.panels.ui.compose.toolbar.EditModeButton
 import com.android.systemui.qs.panels.ui.viewmodel.PaginatedGridViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.toolbar.EditModeButtonViewModel
 import com.android.systemui.qs.ui.compose.borderOnFocus
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
index c6141a1..a05747d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
@@ -28,6 +28,7 @@
 import androidx.compose.foundation.LocalOverscrollFactory
 import androidx.compose.foundation.background
 import androidx.compose.foundation.border
+import androidx.compose.foundation.clipScrollableContainer
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.layout.Arrangement.spacedBy
 import androidx.compose.foundation.layout.Box
@@ -138,7 +139,6 @@
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.shared.model.groupAndSort
 import com.android.systemui.res.R
-import kotlin.math.max
 import kotlin.math.roundToInt
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.delay
@@ -148,8 +148,9 @@
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
 private fun EditModeTopBar(onStopEditing: () -> Unit, onReset: (() -> Unit)?) {
+
     TopAppBar(
-        colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Black),
+        colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent),
         title = { Text(text = stringResource(id = R.string.qs_edit)) },
         navigationIcon = {
             IconButton(onClick = onStopEditing) {
@@ -209,7 +210,15 @@
             Column(
                 verticalArrangement =
                     spacedBy(dimensionResource(id = R.dimen.qs_label_container_margin)),
-                modifier = modifier.fillMaxSize().verticalScroll(scrollState).padding(innerPadding),
+                modifier =
+                    modifier
+                        .fillMaxSize()
+                        // Apply top padding before the scroll so the scrollable doesn't show under
+                        // the
+                        // top bar
+                        .padding(top = innerPadding.calculateTopPadding())
+                        .clipScrollableContainer(Orientation.Vertical)
+                        .verticalScroll(scrollState),
             ) {
                 AnimatedContent(
                     targetState = listState.dragInProgress,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditModeButton.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/EditModeButton.kt
similarity index 93%
rename from packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditModeButton.kt
rename to packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/EditModeButton.kt
index c2764f9..85db952 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditModeButton.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/EditModeButton.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.panels.ui.compose
+package com.android.systemui.qs.panels.ui.compose.toolbar
 
 import androidx.compose.foundation.shape.CornerSize
 import androidx.compose.foundation.shape.RoundedCornerShape
@@ -30,7 +30,7 @@
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import com.android.systemui.lifecycle.rememberViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.EditModeButtonViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.toolbar.EditModeButtonViewModel
 import com.android.systemui.qs.ui.compose.borderOnFocus
 import com.android.systemui.res.R
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/Toolbar.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/Toolbar.kt
new file mode 100644
index 0000000..37fa9e7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/toolbar/Toolbar.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.qs.panels.ui.compose.toolbar
+
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.requiredHeight
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.android.systemui.compose.modifiers.sysuiResTag
+import com.android.systemui.lifecycle.rememberViewModel
+import com.android.systemui.qs.footer.ui.compose.IconButton
+import com.android.systemui.qs.panels.ui.viewmodel.toolbar.ToolbarViewModel
+
+@Composable
+fun Toolbar(toolbarViewModelFactory: ToolbarViewModel.Factory, modifier: Modifier = Modifier) {
+    val viewModel = rememberViewModel("Toolbar") { toolbarViewModelFactory.create() }
+
+    Row(
+        modifier = modifier.fillMaxWidth().requiredHeight(48.dp),
+        verticalAlignment = Alignment.CenterVertically,
+    ) {
+        viewModel.userSwitcherViewModel?.let {
+            IconButton(it, Modifier.sysuiResTag("multi_user_switch"))
+        }
+
+        EditModeButton(viewModel.editModeButtonViewModelFactory)
+
+        IconButton(
+            viewModel.settingsButtonViewModel,
+            Modifier.sysuiResTag("settings_button_container"),
+        )
+
+        Spacer(modifier = Modifier.weight(1f))
+        IconButton(viewModel.powerButtonViewModel, Modifier.sysuiResTag("pm_lite"))
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt
index 4a18872..3fcb2ab 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.lifecycle.Hydrator
 import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QS
 import com.android.systemui.qs.panels.domain.interactor.PaginatedGridInteractor
+import com.android.systemui.qs.panels.ui.viewmodel.toolbar.EditModeButtonViewModel
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import kotlinx.coroutines.awaitCancellation
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/EditModeButtonViewModel.kt
similarity index 90%
rename from packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModel.kt
rename to packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/EditModeButtonViewModel.kt
index b033473..f606218 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/EditModeButtonViewModel.kt
@@ -14,10 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.panels.ui.viewmodel
+package com.android.systemui.qs.panels.ui.viewmodel.toolbar
 
 import com.android.systemui.classifier.domain.interactor.FalsingInteractor
 import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.qs.panels.ui.viewmodel.EditModeViewModel
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/ToolbarViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/ToolbarViewModel.kt
new file mode 100644
index 0000000..0fde855
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/ToolbarViewModel.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.qs.panels.ui.viewmodel.toolbar
+
+import android.content.Context
+import android.view.ContextThemeWrapper
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import com.android.systemui.animation.Expandable
+import com.android.systemui.classifier.domain.interactor.FalsingInteractor
+import com.android.systemui.classifier.domain.interactor.runIfNotFalseTap
+import com.android.systemui.globalactions.GlobalActionsDialogLite
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
+import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractor
+import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsButtonViewModel
+import com.android.systemui.qs.footer.ui.viewmodel.powerButtonViewModel
+import com.android.systemui.qs.footer.ui.viewmodel.settingsButtonViewModel
+import com.android.systemui.qs.footer.ui.viewmodel.userSwitcherViewModel
+import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import javax.inject.Provider
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+
+class ToolbarViewModel
+@AssistedInject
+constructor(
+    val editModeButtonViewModelFactory: EditModeButtonViewModel.Factory,
+    private val footerActionsInteractor: FooterActionsInteractor,
+    private val globalActionsDialogLiteProvider: Provider<GlobalActionsDialogLite>,
+    private val falsingInteractor: FalsingInteractor,
+    @ShadeDisplayAware appContext: Context,
+) : ExclusiveActivatable() {
+    private val qsThemedContext =
+        ContextThemeWrapper(appContext, R.style.Theme_SystemUI_QuickSettings)
+    private val hydrator = Hydrator("ToolbarViewModel.hydrator")
+
+    val powerButtonViewModel = powerButtonViewModel(qsThemedContext, ::onPowerButtonClicked)
+
+    val settingsButtonViewModel =
+        settingsButtonViewModel(qsThemedContext, ::onSettingsButtonClicked)
+
+    val userSwitcherViewModel: FooterActionsButtonViewModel? by
+        hydrator.hydratedStateOf(
+            traceName = "userSwitcherViewModel",
+            initialValue = null,
+            source =
+                userSwitcherViewModel(
+                    qsThemedContext,
+                    footerActionsInteractor,
+                    ::onUserSwitcherClicked,
+                ),
+        )
+
+    override suspend fun onActivated(): Nothing {
+        coroutineScope {
+            launch {
+                try {
+                    globalActionsDialogLite = globalActionsDialogLiteProvider.get()
+                    awaitCancellation()
+                } finally {
+                    globalActionsDialogLite?.destroy()
+                }
+            }
+            launch { hydrator.activate() }
+            awaitCancellation()
+        }
+    }
+
+    private var globalActionsDialogLite: GlobalActionsDialogLite? by mutableStateOf(null)
+
+    private fun onPowerButtonClicked(expandable: Expandable) {
+        falsingInteractor.runIfNotFalseTap {
+            globalActionsDialogLite?.let {
+                footerActionsInteractor.showPowerMenuDialog(it, expandable)
+            }
+        }
+    }
+
+    private fun onUserSwitcherClicked(expandable: Expandable) {
+        falsingInteractor.runIfNotFalseTap { footerActionsInteractor.showUserSwitcher(expandable) }
+    }
+
+    private fun onSettingsButtonClicked(expandable: Expandable) {
+        falsingInteractor.runIfNotFalseTap { footerActionsInteractor.showSettings(expandable) }
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(): ToolbarViewModel
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
index 62b1203..91d9079 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.qs.panels.ui.viewmodel.EditModeViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.QuickQuickSettingsViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.TileGridViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.toolbar.ToolbarViewModel
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
@@ -38,6 +39,7 @@
     val tileGridViewModel: TileGridViewModel,
     val editModeViewModel: EditModeViewModel,
     val detailsViewModel: DetailsViewModel,
+    val toolbarViewModelFactory: ToolbarViewModel.Factory,
 ) : ExclusiveActivatable() {
 
     val brightnessSliderViewModel =
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index 31780a5..61ac1a02 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -41,11 +41,11 @@
 import com.android.compose.theme.PlatformTheme
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.Flags
-import com.android.systemui.Flags.communalHubOnMobile
 import com.android.systemui.ambient.touch.TouchMonitor
 import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent
 import com.android.systemui.communal.dagger.Communal
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
 import com.android.systemui.communal.ui.compose.CommunalContainer
 import com.android.systemui.communal.ui.compose.CommunalContent
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
@@ -83,6 +83,7 @@
 @Inject
 constructor(
     private val communalInteractor: CommunalInteractor,
+    private val communalSettingsInteractor: CommunalSettingsInteractor,
     private val communalViewModel: CommunalViewModel,
     private val keyguardInteractor: KeyguardInteractor,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
@@ -514,7 +515,7 @@
         val touchOnUmo = keyguardMediaController.isWithinMediaViewBounds(ev.x.toInt(), ev.y.toInt())
         val touchOnSmartspace =
             lockscreenSmartspaceController.isWithinSmartspaceBounds(ev.x.toInt(), ev.y.toInt())
-        val glanceableHubV2 = communalHubOnMobile()
+        val glanceableHubV2 = communalSettingsInteractor.isV2FlagEnabled()
         if (
             !hubShowing &&
                 (touchOnNotifications || touchOnUmo || touchOnSmartspace || glanceableHubV2)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
index 6f491e7..85b50d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
@@ -35,6 +35,7 @@
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
 import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
 import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
@@ -67,9 +68,17 @@
                                 Flags.statusBarCallChipNotificationIcon() &&
                                     state.notificationIconView != null
                             ) {
+                                StatusBarConnectedDisplays.assertInLegacyMode()
                                 OngoingActivityChipModel.ChipIcon.StatusBarView(
                                     state.notificationIconView
                                 )
+                            } else if (
+                                StatusBarConnectedDisplays.isEnabled &&
+                                    Flags.statusBarCallChipNotificationIcon()
+                            ) {
+                                OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(
+                                    state.notificationKey
+                                )
                             } else {
                                 OngoingActivityChipModel.ChipIcon.SingleColorIcon(phoneIcon)
                             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt
index c57c807..571a3e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.statusbar.chips.StatusBarChipLogTags.pad
 import com.android.systemui.statusbar.chips.StatusBarChipsLog
 import com.android.systemui.statusbar.chips.notification.domain.model.NotificationChipModel
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
 import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
@@ -100,10 +101,15 @@
     private fun ActiveNotificationModel.toNotificationChipModel(): NotificationChipModel? {
         val statusBarChipIconView = this.statusBarChipIconView
         if (statusBarChipIconView == null) {
-            logger.w({ "$str1: Can't show chip because status bar chip icon view is null" }) {
-                str1 = extraLogTag
+            if (!StatusBarConnectedDisplays.isEnabled) {
+                logger.w({ "$str1: Can't show chip because status bar chip icon view is null" }) {
+                    str1 = extraLogTag
+                }
+                // When the flag is disabled, we keep the old behavior of returning null.
+                // When the flag is enabled, the icon will always be null, and will later be
+                // fetched in the UI layer using the notification key.
+                return null
             }
-            return null
         }
         return NotificationChipModel(key, statusBarChipIconView, whenTime)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt
index bc4241d..4588b19 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt
@@ -21,6 +21,6 @@
 /** Modeling all the data needed to render a status bar notification chip. */
 data class NotificationChipModel(
     val key: String,
-    val statusBarChipIconView: StatusBarIconView,
+    val statusBarChipIconView: StatusBarIconView?,
     val whenTime: Long,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
index b2f7e2f..2cd5bb3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
 import com.android.systemui.statusbar.chips.ui.model.ColorsModel
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
@@ -50,7 +51,14 @@
     /** Converts the notification to the [OngoingActivityChipModel] object. */
     private fun NotificationChipModel.toActivityChipModel(): OngoingActivityChipModel.Shown {
         StatusBarNotifChips.assertInNewMode()
-        val icon = OngoingActivityChipModel.ChipIcon.StatusBarView(this.statusBarChipIconView)
+        val icon =
+            if (this.statusBarChipIconView != null) {
+                StatusBarConnectedDisplays.assertInLegacyMode()
+                OngoingActivityChipModel.ChipIcon.StatusBarView(this.statusBarChipIconView)
+            } else {
+                StatusBarConnectedDisplays.assertInNewMode()
+                OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(this.key)
+            }
         // TODO(b/364653005): Use the notification color if applicable.
         val colors = ColorsModel.Themed
         val onClickListener =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
index 730784a..cf69d40 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
@@ -32,11 +32,13 @@
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
 import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
 import com.android.systemui.statusbar.chips.ui.view.ChipChronometer
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore
 
 /** Binder for ongoing activity chip views. */
 object OngoingActivityChipBinder {
     /** Binds the given [chipModel] data to the given [chipView]. */
-    fun bind(chipModel: OngoingActivityChipModel, chipView: View) {
+    fun bind(chipModel: OngoingActivityChipModel, chipView: View, iconViewStore: IconViewStore?) {
         val chipContext = chipView.context
         val chipDefaultIconView: ImageView =
             chipView.requireViewById(R.id.ongoing_activity_chip_icon)
@@ -51,7 +53,7 @@
         when (chipModel) {
             is OngoingActivityChipModel.Shown -> {
                 // Data
-                setChipIcon(chipModel, chipBackgroundView, chipDefaultIconView)
+                setChipIcon(chipModel, chipBackgroundView, chipDefaultIconView, iconViewStore)
                 setChipMainContent(chipModel, chipTextView, chipTimeView, chipShortTimeDeltaView)
                 chipView.setOnClickListener(chipModel.onClickListener)
                 updateChipPadding(
@@ -85,6 +87,7 @@
         chipModel: OngoingActivityChipModel.Shown,
         backgroundView: ChipBackgroundContainer,
         defaultIconView: ImageView,
+        iconViewStore: IconViewStore?,
     ) {
         // Always remove any previously set custom icon. If we have a new custom icon, we'll re-add
         // it.
@@ -108,40 +111,64 @@
                 defaultIconView.untintView()
             }
             is OngoingActivityChipModel.ChipIcon.StatusBarView -> {
-                // Hide the default icon since we'll show this custom icon instead.
-                defaultIconView.visibility = View.GONE
-
-                // Add the new custom icon:
-                // 1. Set up the right visual params.
-                val iconView = icon.impl
-                with(iconView) {
-                    id = CUSTOM_ICON_VIEW_ID
-                    // TODO(b/354930838): Update the content description to not include "phone" and
-                    // maybe include the app name.
-                    contentDescription =
-                        context.resources.getString(R.string.ongoing_phone_call_content_description)
-                    tintView(iconTint)
+                StatusBarConnectedDisplays.assertInLegacyMode()
+                setStatusBarIconView(defaultIconView, icon.impl, iconTint, backgroundView)
+            }
+            is OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon -> {
+                StatusBarConnectedDisplays.assertInNewMode()
+                val iconView = fetchStatusBarIconView(iconViewStore, icon)
+                if (iconView == null) {
+                    // This means that the notification key doesn't exist anymore.
+                    return
                 }
-
-                // 2. If we just reinflated the view, we may need to detach the icon view from the
-                // old chip before we reattach it to the new one.
-                // See also: NotificationIconContainerViewBinder#bindIcons.
-                val currentParent = iconView.parent as? ViewGroup
-                if (currentParent != null && currentParent != backgroundView) {
-                    currentParent.removeView(iconView)
-                    currentParent.removeTransientView(iconView)
-                }
-
-                // 3: Add the icon as the starting view.
-                backgroundView.addView(
-                    iconView,
-                    /* index= */ 0,
-                    generateCustomIconLayoutParams(iconView),
-                )
+                setStatusBarIconView(defaultIconView, iconView, iconTint, backgroundView)
             }
         }
     }
 
+    private fun fetchStatusBarIconView(
+        iconViewStore: IconViewStore?,
+        icon: OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon,
+    ): StatusBarIconView? {
+        StatusBarConnectedDisplays.assertInNewMode()
+        if (iconViewStore == null) {
+            throw IllegalStateException("Store should always be non-null when flag is enabled.")
+        }
+        return iconViewStore.iconView(icon.notificationKey)
+    }
+
+    private fun setStatusBarIconView(
+        defaultIconView: ImageView,
+        iconView: StatusBarIconView,
+        iconTint: Int,
+        backgroundView: ChipBackgroundContainer,
+    ) {
+        // Hide the default icon since we'll show this custom icon instead.
+        defaultIconView.visibility = View.GONE
+
+        // 1. Set up the right visual params.
+        with(iconView) {
+            id = CUSTOM_ICON_VIEW_ID
+            // TODO(b/354930838): Update the content description to not include "phone" and maybe
+            // include the app name.
+            contentDescription =
+                context.resources.getString(R.string.ongoing_phone_call_content_description)
+            tintView(iconTint)
+        }
+
+        // 2. If we just reinflated the view, we may need to detach the icon view from the old chip
+        // before we reattach it to the new one.
+        // See also: NotificationIconContainerViewBinder#bindIcons.
+        val currentParent = iconView.parent as? ViewGroup
+        if (currentParent != null && currentParent != backgroundView) {
+            currentParent.removeView(iconView)
+            currentParent.removeTransientView(iconView)
+        }
+
+        // 3: Add the icon as the starting view.
+        backgroundView.addView(iconView, /* index= */ 0, generateCustomIconLayoutParams(iconView))
+    }
+
     private fun View.getCustomIconView(): StatusBarIconView? {
         return this.findViewById(CUSTOM_ICON_VIEW_ID)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
index cf07af1..2dce4e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
 
 /** Model representing the display of an ongoing activity as a chip in the status bar. */
 sealed class OngoingActivityChipModel {
@@ -132,6 +133,17 @@
                     "OngoingActivityChipModel.ChipIcon.StatusBarView created even though " +
                         "Flags.statusBarCallChipNotificationIcon is not enabled"
                 }
+                StatusBarConnectedDisplays.assertInLegacyMode()
+            }
+        }
+
+        /**
+         * The icon is a custom icon, which is set on a notification, and can be looked up using the
+         * provided [notificationKey]. The icon was likely created by an external app.
+         */
+        data class StatusBarNotificationIcon(val notificationKey: String) : ChipIcon {
+            init {
+                StatusBarConnectedDisplays.assertInNewMode()
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
index 98ce163..b56a838 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
@@ -162,7 +162,9 @@
             val sbIcon = iconBuilder.createIconView(entry)
             sbIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
             val sbChipIcon: StatusBarIconView?
-            if (Flags.statusBarCallChipNotificationIcon()) {
+            if (
+                Flags.statusBarCallChipNotificationIcon() && !StatusBarConnectedDisplays.isEnabled
+            ) {
                 sbChipIcon = iconBuilder.createIconView(entry)
                 sbChipIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
             } else {
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 e4768e8..724ba8c 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
@@ -377,6 +377,7 @@
         mCarrierConfigTracker.addDefaultDataSubscriptionChangedListener(mDefaultDataListener);
 
         mHomeStatusBarViewBinder.bind(
+                view.getContext().getDisplayId(),
                 mStatusBar,
                 mHomeStatusBarViewModel,
                 /* systemEventChipAnimateIn */ null,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index 2166304..c57cede 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -260,6 +260,7 @@
                 startTimeMs = currentInfo.callStartTime,
                 notificationIconView = icon,
                 intent = currentInfo.intent,
+                notificationKey = currentInfo.key,
             )
         } else {
             return OngoingCallModel.NoCall
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt
index 1f7bd14..4b71c02 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt
@@ -90,6 +90,7 @@
                                     startTimeMs = model.whenTime,
                                     notificationIconView = model.statusBarChipIconView,
                                     intent = model.contentIntent,
+                                    notificationKey = model.key,
                                 )
                             }
                         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt
index c2c91b2..1a5dcc1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt
@@ -46,5 +46,6 @@
         val startTimeMs: Long,
         val notificationIconView: StatusBarIconView?,
         val intent: PendingIntent?,
+        val notificationKey: String,
     ) : OngoingCallModel
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
index 72df027..d9b2bd1d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
@@ -20,9 +20,9 @@
 import android.animation.AnimatorListenerAdapter
 import android.view.View
 import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.app.animation.Interpolators
-import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.Flags
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.lifecycle.repeatWhenAttached
@@ -31,16 +31,19 @@
 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
 import com.android.systemui.statusbar.chips.ui.binder.OngoingActivityChipBinder
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
 import com.android.systemui.statusbar.core.StatusBarRootModernization
 import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState
 import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.AnimatingIn
 import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.AnimatingOut
 import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationState.RunningChipAnim
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ConnectedDisplaysStatusBarNotificationIconViewStore
 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
 import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment
 import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel
 import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.VisibilityModel
 import javax.inject.Inject
+import kotlinx.coroutines.launch
 
 /**
  * Interface to assist with binding the [CollapsedStatusBarFragment] to [HomeStatusBarViewModel].
@@ -56,6 +59,7 @@
      * to support the chip animations.
      */
     fun bind(
+        displayId: Int,
         view: View,
         viewModel: HomeStatusBarViewModel,
         systemEventChipAnimateIn: ((View) -> Unit)?,
@@ -65,8 +69,13 @@
 }
 
 @SysUISingleton
-class HomeStatusBarViewBinderImpl @Inject constructor() : HomeStatusBarViewBinder {
+class HomeStatusBarViewBinderImpl
+@Inject
+constructor(
+    private val viewStoreFactory: ConnectedDisplaysStatusBarNotificationIconViewStore.Factory
+) : HomeStatusBarViewBinder {
     override fun bind(
+        displayId: Int,
         view: View,
         viewModel: HomeStatusBarViewModel,
         systemEventChipAnimateIn: ((View) -> Unit)?,
@@ -75,6 +84,14 @@
     ) {
         view.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.CREATED) {
+                val iconViewStore =
+                    if (StatusBarConnectedDisplays.isEnabled) {
+                        viewStoreFactory.create(displayId).also {
+                            lifecycleScope.launch { it.activate() }
+                        }
+                    } else {
+                        null
+                    }
                 launch {
                     viewModel.isTransitioningFromLockscreenToOccluded.collect {
                         listener.onStatusBarVisibilityMaybeChanged()
@@ -102,7 +119,11 @@
                         view.requireViewById(R.id.ongoing_activity_chip_primary)
                     launch {
                         viewModel.primaryOngoingActivityChip.collect { primaryChipModel ->
-                            OngoingActivityChipBinder.bind(primaryChipModel, primaryChipView)
+                            OngoingActivityChipBinder.bind(
+                                primaryChipModel,
+                                primaryChipView,
+                                iconViewStore,
+                            )
                             if (StatusBarRootModernization.isEnabled) {
                                 when (primaryChipModel) {
                                     is OngoingActivityChipModel.Shown ->
@@ -142,10 +163,18 @@
                         view.requireViewById(R.id.ongoing_activity_chip_secondary)
                     launch {
                         viewModel.ongoingActivityChips.collect { chips ->
-                            OngoingActivityChipBinder.bind(chips.primary, primaryChipView)
+                            OngoingActivityChipBinder.bind(
+                                chips.primary,
+                                primaryChipView,
+                                iconViewStore,
+                            )
                             // TODO(b/364653005): Don't show the secondary chip if there isn't
                             // enough space for it.
-                            OngoingActivityChipBinder.bind(chips.secondary, secondaryChipView)
+                            OngoingActivityChipBinder.bind(
+                                chips.secondary,
+                                secondaryChipView,
+                                iconViewStore,
+                            )
 
                             if (StatusBarRootModernization.isEnabled) {
                                 primaryChipView.adjustVisibility(chips.primary.toVisibilityModel())
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
index 812e0eb..5614d82 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
@@ -176,6 +176,7 @@
                     // This binder handles everything else
                     scope.launch {
                         statusBarViewBinder.bind(
+                            context.displayId,
                             phoneStatusBarView,
                             statusBarViewModel,
                             eventAnimationInteractor::animateStatusBarContentForChipEnter,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index e7fb470c..c410111 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -33,7 +33,7 @@
 import androidx.test.filters.SmallTest
 import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.Flags
-import com.android.systemui.Flags.FLAG_COMMUNAL_HUB_ON_MOBILE
+import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
 import com.android.systemui.Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.ambient.touch.TouchHandler
@@ -43,7 +43,9 @@
 import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository
 import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
 import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
 import com.android.systemui.communal.domain.interactor.setCommunalAvailable
+import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.ui.compose.CommunalContent
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
@@ -131,20 +133,26 @@
             underTest =
                 GlanceableHubContainerController(
                     communalInteractor,
+                    communalSettingsInteractor,
                     communalViewModel,
                     keyguardInteractor,
-                    kosmos.keyguardTransitionInteractor,
+                    keyguardTransitionInteractor,
                     shadeInteractor,
                     powerManager,
                     communalColors,
                     ambientTouchComponentFactory,
                     communalContent,
-                    kosmos.sceneDataSourceDelegator,
-                    kosmos.notificationStackScrollLayoutController,
-                    kosmos.keyguardMediaController,
-                    kosmos.lockscreenSmartspaceController,
+                    sceneDataSourceDelegator,
+                    notificationStackScrollLayoutController,
+                    keyguardMediaController,
+                    lockscreenSmartspaceController,
                     logcatLogBuffer("GlanceableHubContainerControllerTest"),
                 )
+
+            // Make below last notification true by default or else touches will be ignored by
+            // default when the hub is not showing.
+            whenever(notificationStackScrollLayoutController.isBelowLastNotification(any(), any()))
+                .thenReturn(true)
         }
         testableLooper = TestableLooper.get(this)
 
@@ -178,6 +186,7 @@
                 underTest =
                     GlanceableHubContainerController(
                         communalInteractor,
+                        kosmos.communalSettingsInteractor,
                         communalViewModel,
                         keyguardInteractor,
                         kosmos.keyguardTransitionInteractor,
@@ -207,6 +216,7 @@
             val underTest =
                 GlanceableHubContainerController(
                     communalInteractor,
+                    kosmos.communalSettingsInteractor,
                     communalViewModel,
                     keyguardInteractor,
                     kosmos.keyguardTransitionInteractor,
@@ -231,6 +241,7 @@
             val underTest =
                 GlanceableHubContainerController(
                     communalInteractor,
+                    kosmos.communalSettingsInteractor,
                     communalViewModel,
                     keyguardInteractor,
                     kosmos.keyguardTransitionInteractor,
@@ -631,7 +642,7 @@
             }
         }
 
-    @DisableFlags(FLAG_COMMUNAL_HUB_ON_MOBILE)
+    @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
     @Test
     fun onTouchEvent_shadeInteracting_movesNotDispatched() =
         with(kosmos) {
@@ -688,7 +699,7 @@
             }
         }
 
-    @DisableFlags(FLAG_COMMUNAL_HUB_ON_MOBILE)
+    @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
     @Test
     fun onTouchEvent_bouncerInteracting_movesNotDispatched() =
         with(kosmos) {
@@ -721,11 +732,13 @@
             }
         }
 
-    @EnableFlags(FLAG_COMMUNAL_HUB_ON_MOBILE)
+    @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
     @Test
     fun onTouchEvent_onLockscreenAndGlanceableHubV2_touchIgnored() =
         with(kosmos) {
             testScope.runTest {
+                kosmos.setCommunalV2ConfigEnabled(true)
+
                 // On lockscreen.
                 goToScene(CommunalScenes.Blank)
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index 1f68195..ad92b31 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.communal.domain.interactor
 
+import android.content.testableContext
 import android.os.userManager
 import com.android.systemui.broadcast.broadcastDispatcher
 import com.android.systemui.communal.data.repository.communalMediaRepository
@@ -34,6 +35,7 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.plugins.activityStarter
+import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.settings.userTracker
 import com.android.systemui.statusbar.phone.fakeManagedProfileController
@@ -67,6 +69,13 @@
 
 val Kosmos.editWidgetsActivityStarter by Fixture<EditWidgetsActivityStarter> { mock() }
 
+fun Kosmos.setCommunalV2ConfigEnabled(enabled: Boolean) {
+    testableContext.orCreateTestableResources.addOverride(
+        com.android.internal.R.bool.config_glanceableHubEnabled,
+        enabled,
+    )
+}
+
 suspend fun Kosmos.setCommunalEnabled(enabled: Boolean) {
     fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, enabled)
     if (enabled) {
@@ -76,6 +85,15 @@
     }
 }
 
+suspend fun Kosmos.setCommunalV2Enabled(enabled: Boolean) {
+    setCommunalV2ConfigEnabled(true)
+    if (enabled) {
+        fakeUserRepository.asMainUser()
+    } else {
+        fakeUserRepository.asDefaultUser()
+    }
+}
+
 suspend fun Kosmos.setCommunalAvailable(available: Boolean) {
     setCommunalEnabled(available)
     with(fakeKeyguardRepository) {
@@ -83,3 +101,12 @@
         setKeyguardShowing(available)
     }
 }
+
+suspend fun Kosmos.setCommunalV2Available(available: Boolean) {
+    setCommunalV2ConfigEnabled(true)
+    setCommunalEnabled(available)
+    with(fakeKeyguardRepository) {
+        setIsEncryptedOrLockdown(!available)
+        setKeyguardShowing(available)
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/globalactions/GlobalActionsDialogLiteKosmos.kt
similarity index 60%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/globalactions/GlobalActionsDialogLiteKosmos.kt
index b8d3ff4..63bfa52 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/globalactions/GlobalActionsDialogLiteKosmos.kt
@@ -14,16 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.panels.ui.viewmodel
+package com.android.systemui.globalactions
 
-import com.android.systemui.classifier.domain.interactor.falsingInteractor
 import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
 
-val Kosmos.editModeButtonViewModelFactory by
-    Kosmos.Fixture {
-        object : EditModeButtonViewModel.Factory {
-            override fun create(): EditModeButtonViewModel {
-                return EditModeButtonViewModel(editModeViewModel, falsingInteractor)
-            }
-        }
-    }
+/** Provides a mock */
+val Kosmos.globalActionsDialogLite: GlobalActionsDialogLite by Kosmos.Fixture { mock() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
index 10b073e..2e6d8ed 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.FakeStatusBarStateController
 import com.android.systemui.statusbar.StatusBarStateControllerImpl
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.util.mockito.mock
@@ -49,3 +50,6 @@
             { alternateBouncerInteractor },
         )
     }
+
+var Kosmos.fakeStatusBarStateController: SysuiStatusBarStateController by
+    Kosmos.Fixture { FakeStatusBarStateController() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt
index 4acedaa..322b412 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt
@@ -18,5 +18,4 @@
 
 import com.android.systemui.kosmos.Kosmos
 
-var Kosmos.gridLayoutTypeRepository: GridLayoutTypeRepository by
-    Kosmos.Fixture { GridLayoutTypeRepositoryImpl() }
+var Kosmos.gridLayoutTypeRepository by Kosmos.Fixture { GridLayoutTypeRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt
index c951642..40c3c96 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt
@@ -20,10 +20,19 @@
 import com.android.systemui.qs.panels.data.repository.gridLayoutTypeRepository
 import com.android.systemui.qs.panels.shared.model.GridLayoutType
 import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType
+import com.android.systemui.qs.panels.shared.model.PaginatedGridLayoutType
 import com.android.systemui.qs.panels.ui.compose.GridLayout
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.infiniteGridLayout
+import com.android.systemui.qs.panels.ui.compose.paginatedGridLayout
+import com.android.systemui.shade.domain.interactor.shadeModeInteractor
 
 val Kosmos.gridLayoutTypeInteractor by
-    Kosmos.Fixture { GridLayoutTypeInteractor(gridLayoutTypeRepository) }
+    Kosmos.Fixture { GridLayoutTypeInteractor(gridLayoutTypeRepository, shadeModeInteractor) }
 
 val Kosmos.gridLayoutMap: Map<GridLayoutType, GridLayout> by
-    Kosmos.Fixture { mapOf(Pair(InfiniteGridLayoutType, infiniteGridLayout)) }
+    Kosmos.Fixture {
+        mapOf(
+            Pair(InfiniteGridLayoutType, infiniteGridLayout),
+            Pair(PaginatedGridLayoutType, paginatedGridLayout),
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayoutKosmos.kt
similarity index 61%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayoutKosmos.kt
index b8d3ff4..f412d98 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayoutKosmos.kt
@@ -14,16 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.panels.ui.viewmodel
+package com.android.systemui.qs.panels.ui.compose
 
-import com.android.systemui.classifier.domain.interactor.falsingInteractor
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.infiniteGridLayout
+import com.android.systemui.qs.panels.ui.viewmodel.paginatedGridViewModelFactory
 
-val Kosmos.editModeButtonViewModelFactory by
-    Kosmos.Fixture {
-        object : EditModeButtonViewModel.Factory {
-            override fun create(): EditModeButtonViewModel {
-                return EditModeButtonViewModel(editModeViewModel, falsingInteractor)
-            }
-        }
-    }
+val Kosmos.paginatedGridLayout by
+    Kosmos.Fixture { PaginatedGridLayout(paginatedGridViewModelFactory, infiniteGridLayout) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayoutKosmos.kt
similarity index 89%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayoutKosmos.kt
index 6fe860c..f57806c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayoutKosmos.kt
@@ -14,11 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.panels.domain.interactor
+package com.android.systemui.qs.panels.ui.compose.infinitegrid
 
 import com.android.systemui.haptics.msdl.tileHapticsViewModelFactoryProvider
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.qs.panels.ui.compose.infinitegrid.InfiniteGridLayout
 import com.android.systemui.qs.panels.ui.viewmodel.detailsViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.iconTilesViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.infiniteGridViewModelFactory
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelKosmos.kt
index 33227a4..86c3add 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelKosmos.kt
@@ -23,8 +23,8 @@
 import com.android.systemui.qs.panels.domain.interactor.editTilesListInteractor
 import com.android.systemui.qs.panels.domain.interactor.gridLayoutMap
 import com.android.systemui.qs.panels.domain.interactor.gridLayoutTypeInteractor
-import com.android.systemui.qs.panels.domain.interactor.infiniteGridLayout
 import com.android.systemui.qs.panels.domain.interactor.tilesAvailabilityInteractor
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.infiniteGridLayout
 import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
 import com.android.systemui.qs.pipeline.domain.interactor.minimumTilesInteractor
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt
index 5c71ba2..128cfca 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.development.ui.viewmodel.buildNumberViewModelFactory
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.qs.panels.domain.interactor.paginatedGridInteractor
+import com.android.systemui.qs.panels.ui.viewmodel.toolbar.editModeButtonViewModelFactory
 
 val Kosmos.paginatedGridViewModel by
     Kosmos.Fixture {
@@ -33,3 +34,10 @@
             falsingInteractor,
         )
     }
+
+val Kosmos.paginatedGridViewModelFactory by
+    Kosmos.Fixture {
+        object : PaginatedGridViewModel.Factory {
+            override fun create() = paginatedGridViewModel
+        }
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt
index 9481fca..e79d213 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt
@@ -20,7 +20,7 @@
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.qs.panels.domain.interactor.gridLayoutMap
 import com.android.systemui.qs.panels.domain.interactor.gridLayoutTypeInteractor
-import com.android.systemui.qs.panels.domain.interactor.infiniteGridLayout
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.infiniteGridLayout
 import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
 
 val Kosmos.tileGridViewModel by
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/EditModeButtonViewModelKosmos.kt
similarity index 88%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/EditModeButtonViewModelKosmos.kt
index b8d3ff4..8ae1332 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/EditModeButtonViewModelKosmos.kt
@@ -14,10 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.panels.ui.viewmodel
+package com.android.systemui.qs.panels.ui.viewmodel.toolbar
 
 import com.android.systemui.classifier.domain.interactor.falsingInteractor
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.panels.ui.viewmodel.editModeViewModel
 
 val Kosmos.editModeButtonViewModelFactory by
     Kosmos.Fixture {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/ToolbarViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/ToolbarViewModelKosmos.kt
new file mode 100644
index 0000000..52d8a3a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/toolbar/ToolbarViewModelKosmos.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.qs.panels.ui.viewmodel.toolbar
+
+import android.content.applicationContext
+import com.android.systemui.classifier.domain.interactor.falsingInteractor
+import com.android.systemui.globalactions.globalActionsDialogLite
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.footerActionsInteractor
+
+val Kosmos.toolbarViewModelFactory by
+    Kosmos.Fixture {
+        object : ToolbarViewModel.Factory {
+            override fun create(): ToolbarViewModel {
+                return ToolbarViewModel(
+                    editModeButtonViewModelFactory,
+                    footerActionsInteractor,
+                    { globalActionsDialogLite },
+                    falsingInteractor,
+                    applicationContext,
+                )
+            }
+        }
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt
index 6afc0d80..aa6ce9a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.qs.panels.ui.viewmodel.editModeViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.quickQuickSettingsViewModelFactory
 import com.android.systemui.qs.panels.ui.viewmodel.tileGridViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.toolbar.toolbarViewModelFactory
 
 val Kosmos.quickSettingsContainerViewModelFactory by
     Kosmos.Fixture {
@@ -36,6 +37,7 @@
                     tileGridViewModel,
                     editModeViewModel,
                     detailsViewModel,
+                    toolbarViewModelFactory,
                 )
             }
         }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerKosmos.kt
new file mode 100644
index 0000000..fa5bd7a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.statusbar.notification
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.statusbar.notificationLockscreenUserManager
+import com.android.systemui.statusbar.policy.keyguardStateController
+import org.mockito.kotlin.mock
+
+var Kosmos.dynamicPrivacyController: DynamicPrivacyController by
+    Kosmos.Fixture {
+        DynamicPrivacyController(
+            notificationLockscreenUserManager,
+            keyguardStateController,
+            statusBarStateController,
+        )
+    }
+
+var Kosmos.mockDynamicPrivacyController: DynamicPrivacyController by Kosmos.Fixture { mock() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorKosmos.kt
new file mode 100644
index 0000000..4a249a8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorKosmos.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.statusbar.notification.collection.coordinator
+
+import com.android.keyguard.keyguardUpdateMonitor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.statusbar.notification.dynamicPrivacyController
+import com.android.systemui.statusbar.notificationLockscreenUserManager
+import com.android.systemui.statusbar.policy.keyguardStateController
+import com.android.systemui.statusbar.policy.sensitiveNotificationProtectionController
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@OptIn(ExperimentalCoroutinesApi::class)
+var Kosmos.sensitiveContentCoordinator: SensitiveContentCoordinator by
+    Kosmos.Fixture {
+        SensitiveContentCoordinatorImpl(
+            dynamicPrivacyController,
+            notificationLockscreenUserManager,
+            keyguardUpdateMonitor,
+            statusBarStateController,
+            keyguardStateController,
+            selectedUserInteractor,
+            sensitiveNotificationProtectionController,
+            deviceEntryInteractor,
+            sceneInteractor,
+            testScope,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt
index 3963d7c..766b280 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt
@@ -23,5 +23,6 @@
 fun inCallModel(
     startTimeMs: Long,
     notificationIcon: StatusBarIconView? = null,
-    intent: PendingIntent? = null
-) = OngoingCallModel.InCall(startTimeMs, notificationIcon, intent)
+    intent: PendingIntent? = null,
+    notificationKey: String = "test",
+) = OngoingCallModel.InCall(startTimeMs, notificationIcon, intent, notificationKey)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerKosmos.kt
similarity index 60%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerKosmos.kt
index b8d3ff4..8f9184d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeButtonViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerKosmos.kt
@@ -14,16 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.panels.ui.viewmodel
+package com.android.systemui.statusbar.policy
 
-import com.android.systemui.classifier.domain.interactor.falsingInteractor
 import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
 
-val Kosmos.editModeButtonViewModelFactory by
-    Kosmos.Fixture {
-        object : EditModeButtonViewModel.Factory {
-            override fun create(): EditModeButtonViewModel {
-                return EditModeButtonViewModel(editModeViewModel, falsingInteractor)
-            }
-        }
-    }
+var Kosmos.sensitiveNotificationProtectionController: SensitiveNotificationProtectionController by
+    Kosmos.Fixture { mockSensitiveNotificationProtectionController }
+val Kosmos.mockSensitiveNotificationProtectionController:
+    SensitiveNotificationProtectionController by
+    Kosmos.Fixture { mock<SensitiveNotificationProtectionController>() }
diff --git a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
index 4bcfb33..7c8604f 100644
--- a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
+++ b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
@@ -240,9 +240,9 @@
             boolean matchesMacAddress = Objects.equals(
                     associationInfo.getDeviceMacAddress(),
                     restored.getDeviceMacAddress());
-            boolean matchesDeviceId = !Flags.associationTag()
-                    || (associationInfo.getDeviceId() != null
-                        && associationInfo.getDeviceId().isSameDevice(restored.getDeviceId()));
+            boolean matchesDeviceId = Flags.associationTag()
+                    && (associationInfo.getDeviceId() != null
+                    && associationInfo.getDeviceId().isSameDevice(restored.getDeviceId()));
             return matchesMacAddress || matchesDeviceId;
         };
         AssociationInfo local = CollectionUtils.find(localAssociations, isSameDevice);
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 3ce6451..719928b 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -2846,6 +2846,14 @@
                                 : AccountsDb.DEBUG_ACTION_SET_PASSWORD;
                         logRecord(action, AccountsDb.TABLE_ACCOUNTS, accountId, accounts,
                                 callingUid);
+
+                        FrameworkStatsLog.write(
+                                FrameworkStatsLog.ACCOUNT_MANAGER_EVENT,
+                                account.type,
+                                callingUid,
+                                TextUtils.isEmpty(password)
+                                ? FrameworkStatsLog.ACCOUNT_MANAGER_EVENT__EVENT_TYPE__PASSWORD_REMOVED
+                                : FrameworkStatsLog.ACCOUNT_MANAGER_EVENT__EVENT_TYPE__PASSWORD_CHANGED);
                     }
                 } finally {
                     accounts.accountsDb.endTransaction();
@@ -2912,7 +2920,7 @@
             if (!accountExistsCache(accounts, account)) {
                 return;
             }
-            setUserdataInternal(accounts, account, key, value);
+            setUserdataInternal(accounts, account, key, value, callingUid);
         } finally {
             restoreCallingIdentity(identityToken);
         }
@@ -2932,7 +2940,7 @@
     }
 
     private void setUserdataInternal(UserAccounts accounts, Account account, String key,
-            String value) {
+            String value, int callingUid) {
         synchronized (accounts.dbLock) {
             accounts.accountsDb.beginTransaction();
             try {
@@ -2958,6 +2966,11 @@
                 AccountManager.invalidateLocalAccountUserDataCaches();
             }
         }
+        FrameworkStatsLog.write(
+                FrameworkStatsLog.ACCOUNT_MANAGER_EVENT,
+                account.type,
+                callingUid,
+                FrameworkStatsLog.ACCOUNT_MANAGER_EVENT__EVENT_TYPE__USER_DATA_CHANGED);
     }
 
     private void onResult(IAccountManagerResponse response, Bundle result) {
diff --git a/services/core/java/com/android/server/display/plugin/PluginEventStorage.java b/services/core/java/com/android/server/display/plugin/PluginEventStorage.java
new file mode 100644
index 0000000..c58ba55
--- /dev/null
+++ b/services/core/java/com/android/server/display/plugin/PluginEventStorage.java
@@ -0,0 +1,134 @@
+/*
+ * 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.display.plugin;
+
+import android.util.IndentingPrintWriter;
+
+import com.android.internal.util.RingBuffer;
+
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+class PluginEventStorage {
+    private static final long TIME_FRAME_LENGTH = 60_000;
+    private static final long MIN_EVENT_DELAY = 500;
+    private static final int MAX_TIME_FRAMES = 10;
+    // not thread safe
+    private static final SimpleDateFormat sDateFormat = new SimpleDateFormat(
+            "MM-dd HH:mm:ss.SSS", Locale.US);
+
+    RingBuffer<TimeFrame> mEvents = new RingBuffer<>(
+            TimeFrame::new, TimeFrame[]::new, MAX_TIME_FRAMES);
+
+    private final Map<PluginType<?>, Long> mEventTimes = new HashMap<>();
+    private long mTimeFrameStart = 0;
+    private final Map<PluginType<?>, EventCounter> mCounters = new HashMap<>();
+
+    <T> void onValueUpdated(PluginType<T> type) {
+        long eventTime = System.currentTimeMillis();
+        if (eventTime - TIME_FRAME_LENGTH > mTimeFrameStart) { // event is in next TimeFrame
+            closeCurrentTimeFrame();
+            mTimeFrameStart = eventTime;
+        }
+        updateCurrentTimeFrame(type, eventTime);
+    }
+
+    private void closeCurrentTimeFrame() {
+        if (!mCounters.isEmpty()) {
+            mEvents.append(new TimeFrame(
+                    mTimeFrameStart, mTimeFrameStart + TIME_FRAME_LENGTH, mCounters));
+            mCounters.clear();
+        }
+    }
+
+    private <T> void updateCurrentTimeFrame(PluginType<T> type, long eventTime) {
+        EventCounter counter = mCounters.get(type);
+        long previousTimestamp = mEventTimes.getOrDefault(type, 0L);
+        if (counter == null) {
+            counter = new EventCounter();
+            mCounters.put(type, counter);
+        }
+        counter.increase(eventTime, previousTimestamp);
+        mEventTimes.put(type, eventTime);
+    }
+
+    List<TimeFrame> getTimeFrames() {
+        List<TimeFrame> timeFrames = new ArrayList<>(Arrays.stream(mEvents.toArray()).toList());
+        timeFrames.add(new TimeFrame(
+                mTimeFrameStart, System.currentTimeMillis(), mCounters));
+        return timeFrames;
+    }
+
+    static class TimeFrame {
+        private final long mStart;
+        private final long mEnd;
+        private final  Map<PluginType<?>, EventCounter> mCounters;
+
+        private TimeFrame() {
+            this(0, 0, Map.of());
+        }
+
+        private TimeFrame(long start, long end, Map<PluginType<?>, EventCounter> counters) {
+            mStart = start;
+            mEnd = end;
+            mCounters = new HashMap<>(counters);
+        }
+
+        @SuppressWarnings("JavaUtilDate")
+        void dump(PrintWriter pw) {
+            pw.append("TimeFrame:[")
+                    .append(sDateFormat.format(new Date(mStart)))
+                    .append(" - ")
+                    .append(sDateFormat.format(new Date(mEnd)))
+                    .println("]:");
+            IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "    ");
+            if (mCounters.isEmpty()) {
+                ipw.println("NO EVENTS");
+            } else {
+                for (Map.Entry<PluginType<?>, EventCounter> entry: mCounters.entrySet()) {
+                    ipw.append(entry.getKey().mName).append(" -> {");
+                    entry.getValue().dump(ipw);
+                    ipw.println("}");
+                }
+            }
+        }
+    }
+
+    private static class EventCounter {
+        private int mEventCounter = 0;
+        private int mFastEventCounter = 0;
+
+        private void increase(long timestamp, long previousTimestamp) {
+            mEventCounter++;
+            if (timestamp - previousTimestamp < MIN_EVENT_DELAY) {
+                mFastEventCounter++;
+            }
+        }
+
+        private void dump(PrintWriter pw) {
+            pw.append("Count:").append(String.valueOf(mEventCounter))
+                    .append("; Fast:").append(String.valueOf(mFastEventCounter));
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/display/plugin/PluginStorage.java b/services/core/java/com/android/server/display/plugin/PluginStorage.java
index 2bcea77..dd3415f 100644
--- a/services/core/java/com/android/server/display/plugin/PluginStorage.java
+++ b/services/core/java/com/android/server/display/plugin/PluginStorage.java
@@ -25,6 +25,7 @@
 import java.io.PrintWriter;
 import java.util.HashMap;
 import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -39,6 +40,8 @@
     private final Map<PluginType<?>, Object> mValues = new HashMap<>();
     @GuardedBy("mLock")
     private final Map<PluginType<?>, ListenersContainer<?>> mListeners = new HashMap<>();
+    @GuardedBy("mLock")
+    private final PluginEventStorage mPluginEventStorage = new PluginEventStorage();
 
     /**
      * Updates value in storage and forwards it to corresponding listeners.
@@ -50,6 +53,7 @@
         Set<PluginManager.PluginChangeListener<T>> localListeners;
         synchronized (mLock) {
             mValues.put(type, value);
+            mPluginEventStorage.onValueUpdated(type);
             ListenersContainer<T> container = getListenersContainerForTypeLocked(type);
             localListeners = new LinkedHashSet<>(container.mListeners);
         }
@@ -91,13 +95,19 @@
         Map<PluginType<?>, Object> localValues;
         @SuppressWarnings("rawtypes")
         Map<PluginType, Set> localListeners = new HashMap<>();
+        List<PluginEventStorage.TimeFrame> timeFrames;
         synchronized (mLock) {
+            timeFrames = mPluginEventStorage.getTimeFrames();
             localValues = new HashMap<>(mValues);
             mListeners.forEach((type, container) -> localListeners.put(type, container.mListeners));
         }
         pw.println("PluginStorage:");
         pw.println("values=" + localValues);
         pw.println("listeners=" + localListeners);
+        pw.println("PluginEventStorage:");
+        for (PluginEventStorage.TimeFrame timeFrame: timeFrames) {
+            timeFrame.dump(pw);
+        }
     }
 
     @GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubHalEndpointCallback.java b/services/core/java/com/android/server/location/contexthub/ContextHubHalEndpointCallback.java
index 8e72553..9d52c6a 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubHalEndpointCallback.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubHalEndpointCallback.java
@@ -68,7 +68,7 @@
         }
         HubEndpointInfo[] endpointInfos = new HubEndpointInfo[halEndpointInfos.length];
         for (int i = 0; i < halEndpointInfos.length; i++) {
-            endpointInfos[i++] = new HubEndpointInfo(halEndpointInfos[i]);
+            endpointInfos[i] = new HubEndpointInfo(halEndpointInfos[i]);
         }
         mEndpointLifecycleCallback.onEndpointStarted(endpointInfos);
     }
diff --git a/services/tests/ondeviceintelligencetests/Android.bp b/services/tests/ondeviceintelligencetests/Android.bp
index a31a3fb..f235da2 100644
--- a/services/tests/ondeviceintelligencetests/Android.bp
+++ b/services/tests/ondeviceintelligencetests/Android.bp
@@ -44,14 +44,18 @@
         "truth",
         "frameworks-base-testutils",
         "androidx.test.rules",
-    ],
+    ] + select(soong_config_variable("ANDROID", "release_ondevice_intelligence_module"), {
+        "true": [
+            "service-ondeviceintelligence.impl",
+        ],
+        default: [],
+    }),
 
     libs: [
         "android.test.mock.stubs.system",
         "android.test.base.stubs.system",
         "android.test.runner.stubs.system",
     ],
-
     certificate: "platform",
     platform_apis: true,
     test_suites: ["device-tests"],
diff --git a/services/tests/ondeviceintelligencetests/src/com/android/server/ondeviceintelligence/InferenceInfoStoreTest.java b/services/tests/ondeviceintelligencetests/src/com/android/server/ondeviceintelligence/InferenceInfoStoreTest.java
index d12579c..28ccb84 100644
--- a/services/tests/ondeviceintelligencetests/src/com/android/server/ondeviceintelligence/InferenceInfoStoreTest.java
+++ b/services/tests/ondeviceintelligencetests/src/com/android/server/ondeviceintelligence/InferenceInfoStoreTest.java
@@ -21,7 +21,6 @@
 import android.app.ondeviceintelligence.InferenceInfo;
 import android.os.Bundle;
 import android.os.PersistableBundle;
-import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService;
 import android.util.Base64;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -38,6 +37,8 @@
 public class InferenceInfoStoreTest {
     InferenceInfoStore inferenceInfoStore;
 
+    public static final String INFERENCE_INFO_BUNDLE_KEY = "inference_info";
+
     @Before
     public void setUp() {
         inferenceInfoStore = new InferenceInfoStore(1000);
@@ -46,7 +47,7 @@
     @Test
     public void testInferenceInfoParsesFromBundleSuccessfully() throws Exception {
         Bundle bundle = new Bundle();
-        bundle.putByteArray(OnDeviceSandboxedInferenceService.INFERENCE_INFO_BUNDLE_KEY,
+        bundle.putByteArray(INFERENCE_INFO_BUNDLE_KEY,
                 getInferenceInfoBytes(1, 1, 100));
         inferenceInfoStore.addInferenceInfoFromBundle(bundle);
         List<InferenceInfo> inferenceInfos = inferenceInfoStore.getLatestInferenceInfo(0);
@@ -59,7 +60,7 @@
     @Test
     public void testInferenceInfoParsesFromPersistableBundleSuccessfully() throws Exception {
         PersistableBundle bundle = new PersistableBundle();
-        bundle.putString(OnDeviceSandboxedInferenceService.INFERENCE_INFO_BUNDLE_KEY,
+        bundle.putString(INFERENCE_INFO_BUNDLE_KEY,
                 Base64.encodeToString(getInferenceInfoBytes(1, 1, 100), Base64.DEFAULT));
         inferenceInfoStore.addInferenceInfoFromBundle(bundle);
         List<InferenceInfo> inferenceInfos = inferenceInfoStore.getLatestInferenceInfo(0);
@@ -74,11 +75,11 @@
     public void testEvictionAfterMaxAge() throws Exception {
         PersistableBundle bundle = new PersistableBundle();
         long testStartTime = System.currentTimeMillis();
-        bundle.putString(OnDeviceSandboxedInferenceService.INFERENCE_INFO_BUNDLE_KEY,
+        bundle.putString(INFERENCE_INFO_BUNDLE_KEY,
                 Base64.encodeToString(getInferenceInfoBytes(1,  testStartTime - 10,
                         testStartTime + 100), Base64.DEFAULT));
         inferenceInfoStore.addInferenceInfoFromBundle(bundle);
-        bundle.putString(OnDeviceSandboxedInferenceService.INFERENCE_INFO_BUNDLE_KEY,
+        bundle.putString(INFERENCE_INFO_BUNDLE_KEY,
                 Base64.encodeToString(getInferenceInfoBytes(1, testStartTime - 5,
                         testStartTime + 100), Base64.DEFAULT));
         inferenceInfoStore.addInferenceInfoFromBundle(bundle);
diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
index 0404b82..c4b8599 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
@@ -118,6 +118,7 @@
 import java.time.LocalDateTime;
 import java.time.LocalTime;
 import java.time.ZoneId;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Consumer;
@@ -232,7 +233,8 @@
                 any(AlarmManager.OnAlarmListener.class), any(Handler.class));
 
         doAnswer(inv -> {
-            mCustomListener = () -> {};
+            mCustomListener = () -> {
+            };
             return null;
         }).when(mAlarmManager).cancel(eq(mCustomListener));
         when(mContext.getSystemService(eq(Context.POWER_SERVICE)))
@@ -1321,7 +1323,7 @@
     @Test
     public void enableCarMode_failsForBogusPackageName() throws Exception {
         when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
-            .thenReturn(TestInjector.DEFAULT_CALLING_UID + 1);
+                .thenReturn(TestInjector.DEFAULT_CALLING_UID + 1);
 
         assertThrows(SecurityException.class, () -> mService.enableCarMode(0, 0, PACKAGE_NAME));
         assertThat(mService.getCurrentModeType()).isNotEqualTo(Configuration.UI_MODE_TYPE_CAR);
@@ -1343,19 +1345,19 @@
     @Test
     public void disableCarMode_failsForBogusPackageName() throws Exception {
         when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
-            .thenReturn(TestInjector.DEFAULT_CALLING_UID);
+                .thenReturn(TestInjector.DEFAULT_CALLING_UID);
         mService.enableCarMode(0, 0, PACKAGE_NAME);
         assertThat(mService.getCurrentModeType()).isEqualTo(Configuration.UI_MODE_TYPE_CAR);
         when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
-            .thenReturn(TestInjector.DEFAULT_CALLING_UID + 1);
+                .thenReturn(TestInjector.DEFAULT_CALLING_UID + 1);
 
         assertThrows(SecurityException.class,
-            () -> mService.disableCarModeByCallingPackage(0, PACKAGE_NAME));
+                () -> mService.disableCarModeByCallingPackage(0, PACKAGE_NAME));
         assertThat(mService.getCurrentModeType()).isEqualTo(Configuration.UI_MODE_TYPE_CAR);
 
         // Clean up
         when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
-            .thenReturn(TestInjector.DEFAULT_CALLING_UID);
+                .thenReturn(TestInjector.DEFAULT_CALLING_UID);
         mService.disableCarModeByCallingPackage(0, PACKAGE_NAME);
         assertThat(mService.getCurrentModeType()).isNotEqualTo(Configuration.UI_MODE_TYPE_CAR);
     }
@@ -1473,12 +1475,13 @@
             assertFalse(mUiManagerService.getConfiguration().isNightModeActive());
         }
 
-        // attention modes with expected night modes
-        Map<Integer, Boolean> modes = Map.of(
-                MODE_ATTENTION_THEME_OVERLAY_OFF, initialNightMode,
-                MODE_ATTENTION_THEME_OVERLAY_DAY, false,
-                MODE_ATTENTION_THEME_OVERLAY_NIGHT, true
-        );
+        // Attention modes with expected night modes.
+        // Important to keep modes.put(MODE_ATTENTION_THEME_OVERLAY_OFF, initialNightMode) in the
+        // first position, hence LinkedHashMap.
+        Map<Integer, Boolean> modes = new LinkedHashMap<>();
+        modes.put(MODE_ATTENTION_THEME_OVERLAY_OFF, initialNightMode);
+        modes.put(MODE_ATTENTION_THEME_OVERLAY_DAY, false);
+        modes.put(MODE_ATTENTION_THEME_OVERLAY_NIGHT, true);
 
         // test
         for (int attentionMode : modes.keySet()) {
@@ -1562,7 +1565,7 @@
         private final int callingUid;
 
         public TestInjector() {
-          this(DEFAULT_CALLING_UID);
+            this(DEFAULT_CALLING_UID);
         }
 
         public TestInjector(int callingUid) {