Merge "[Notif redesign] Swap history and settings buttons" into main
diff --git a/apex/jobscheduler/service/aconfig/alarm.aconfig b/apex/jobscheduler/service/aconfig/alarm.aconfig
index d3068d7..a6e9807 100644
--- a/apex/jobscheduler/service/aconfig/alarm.aconfig
+++ b/apex/jobscheduler/service/aconfig/alarm.aconfig
@@ -2,16 +2,6 @@
 container: "system"
 
 flag {
-    name: "use_frozen_state_to_drop_listener_alarms"
-    namespace: "backstage_power"
-    description: "Use frozen state callback to drop listener alarms for cached apps"
-    bug: "324470945"
-    metadata {
-      purpose: PURPOSE_BUGFIX
-    }
-}
-
-flag {
     name: "start_user_before_scheduled_alarms"
     namespace: "multiuser"
     description: "Persist list of users with alarms scheduled and wakeup stopped users before alarms are due"
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 033da2d..60ba3b8 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -282,7 +282,6 @@
 
     private final Injector mInjector;
     int mBroadcastRefCount = 0;
-    boolean mUseFrozenStateToDropListenerAlarms;
     MetricsHelper mMetricsHelper;
     PowerManager.WakeLock mWakeLock;
     SparseIntArray mAlarmsPerUid = new SparseIntArray();
@@ -1784,40 +1783,37 @@
         mMetricsHelper = new MetricsHelper(getContext(), mLock);
         mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
 
-        mUseFrozenStateToDropListenerAlarms = Flags.useFrozenStateToDropListenerAlarms();
         mStartUserBeforeScheduledAlarms = Flags.startUserBeforeScheduledAlarms()
                 && UserManager.supportsMultipleUsers();
         if (mStartUserBeforeScheduledAlarms) {
             mUserWakeupStore = new UserWakeupStore();
             mUserWakeupStore.init();
         }
-        if (mUseFrozenStateToDropListenerAlarms) {
-            final ActivityManager.UidFrozenStateChangedCallback callback = (uids, frozenStates) -> {
-                final int size = frozenStates.length;
-                if (uids.length != size) {
-                    Slog.wtf(TAG, "Got different length arrays in frozen state callback!"
-                            + " uids.length: " + uids.length + " frozenStates.length: " + size);
-                    // Cannot process received data in any meaningful way.
-                    return;
+        final ActivityManager.UidFrozenStateChangedCallback callback = (uids, frozenStates) -> {
+            final int size = frozenStates.length;
+            if (uids.length != size) {
+                Slog.wtf(TAG, "Got different length arrays in frozen state callback!"
+                        + " uids.length: " + uids.length + " frozenStates.length: " + size);
+                // Cannot process received data in any meaningful way.
+                return;
+            }
+            final IntArray affectedUids = new IntArray();
+            for (int i = 0; i < size; i++) {
+                if (frozenStates[i] != UID_FROZEN_STATE_FROZEN) {
+                    continue;
                 }
-                final IntArray affectedUids = new IntArray();
-                for (int i = 0; i < size; i++) {
-                    if (frozenStates[i] != UID_FROZEN_STATE_FROZEN) {
-                        continue;
-                    }
-                    if (!CompatChanges.isChangeEnabled(EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED,
-                            uids[i])) {
-                        continue;
-                    }
-                    affectedUids.add(uids[i]);
+                if (!CompatChanges.isChangeEnabled(EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED,
+                        uids[i])) {
+                    continue;
                 }
-                if (affectedUids.size() > 0) {
-                    removeExactListenerAlarms(affectedUids.toArray());
-                }
-            };
-            final ActivityManager am = getContext().getSystemService(ActivityManager.class);
-            am.registerUidFrozenStateChangedCallback(new HandlerExecutor(mHandler), callback);
-        }
+                affectedUids.add(uids[i]);
+            }
+            if (affectedUids.size() > 0) {
+                removeExactListenerAlarms(affectedUids.toArray());
+            }
+        };
+        final ActivityManager am = getContext().getSystemService(ActivityManager.class);
+        am.registerUidFrozenStateChangedCallback(new HandlerExecutor(mHandler), callback);
 
         mListenerDeathRecipient = new IBinder.DeathRecipient() {
             @Override
@@ -2994,13 +2990,10 @@
 
             pw.println("Feature Flags:");
             pw.increaseIndent();
-            pw.print(Flags.FLAG_USE_FROZEN_STATE_TO_DROP_LISTENER_ALARMS,
-                    mUseFrozenStateToDropListenerAlarms);
-            pw.println();
             pw.print(Flags.FLAG_START_USER_BEFORE_SCHEDULED_ALARMS,
                     Flags.startUserBeforeScheduledAlarms());
-            pw.decreaseIndent();
             pw.println();
+            pw.decreaseIndent();
             pw.println();
 
             pw.println("App Standby Parole: " + mAppStandbyParole);
@@ -5146,38 +5139,6 @@
                 removeForStoppedLocked(uid);
             }
         }
-
-        @Override
-        public void handleUidCachedChanged(int uid, boolean cached) {
-            if (mUseFrozenStateToDropListenerAlarms) {
-                // Use ActivityManager#UidFrozenStateChangedCallback instead.
-                return;
-            }
-            if (!CompatChanges.isChangeEnabled(EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED, uid)) {
-                return;
-            }
-            // Apps can quickly get frozen after being cached, breaking the exactness guarantee on
-            // listener alarms. So going forward, the contract of exact listener alarms explicitly
-            // states that they will be removed as soon as the app goes out of lifecycle. We still
-            // allow a short grace period for quick shuffling of proc-states that may happen
-            // unexpectedly when switching between different lifecycles and is generally hard for
-            // apps to avoid.
-
-            final long delay;
-            synchronized (mLock) {
-                delay = mConstants.CACHED_LISTENER_REMOVAL_DELAY;
-            }
-            final Integer uidObj = uid;
-
-            if (cached && !mHandler.hasEqualMessages(REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED,
-                    uidObj)) {
-                mHandler.sendMessageDelayed(
-                        mHandler.obtainMessage(REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED, uidObj),
-                        delay);
-            } else {
-                mHandler.removeEqualMessages(REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED, uidObj);
-            }
-        }
     };
 
     private final BroadcastStats getStatsLocked(PendingIntent pi) {
diff --git a/core/api/current.txt b/core/api/current.txt
index acbd1e6..59dc314 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1276,6 +1276,7 @@
     field public static final int paddingStart = 16843699; // 0x10103b3
     field public static final int paddingTop = 16842967; // 0x10100d7
     field public static final int paddingVertical = 16844094; // 0x101053e
+    field @FlaggedApi("android.content.pm.app_compat_option_16kb") public static final int pageSizeCompat;
     field public static final int panelBackground = 16842846; // 0x101005e
     field public static final int panelColorBackground = 16842849; // 0x1010061
     field public static final int panelColorForeground = 16842848; // 0x1010060
@@ -18826,6 +18827,19 @@
     field public static final int TRANSFER_UNSPECIFIED = 0; // 0x0
   }
 
+  @FlaggedApi("android.hardware.flags.luts_api") public final class DisplayLuts {
+    ctor @FlaggedApi("android.hardware.flags.luts_api") public DisplayLuts();
+    method @FlaggedApi("android.hardware.flags.luts_api") public void set(@NonNull android.hardware.DisplayLuts.Entry);
+    method @FlaggedApi("android.hardware.flags.luts_api") public void set(@NonNull android.hardware.DisplayLuts.Entry, @NonNull android.hardware.DisplayLuts.Entry);
+  }
+
+  @FlaggedApi("android.hardware.flags.luts_api") public static class DisplayLuts.Entry {
+    ctor @FlaggedApi("android.hardware.flags.luts_api") public DisplayLuts.Entry(@NonNull float[], int, int);
+    method @FlaggedApi("android.hardware.flags.luts_api") @NonNull public float[] getBuffer();
+    method @FlaggedApi("android.hardware.flags.luts_api") public int getDimension();
+    method @FlaggedApi("android.hardware.flags.luts_api") public int getSamplingKey();
+  }
+
   public class GeomagneticField {
     ctor public GeomagneticField(float, float, float, long);
     method public float getDeclination();
@@ -18887,8 +18901,19 @@
     field @FlaggedApi("android.media.codec.p210_format_support") public static final int YCBCR_P210 = 60; // 0x3c
   }
 
+  @FlaggedApi("android.hardware.flags.luts_api") public final class LutProperties {
+    method @FlaggedApi("android.hardware.flags.luts_api") public int getDimension();
+    method @FlaggedApi("android.hardware.flags.luts_api") @NonNull public int[] getSamplingKeys();
+    method @FlaggedApi("android.hardware.flags.luts_api") public int getSize();
+    field @FlaggedApi("android.hardware.flags.luts_api") public static final int ONE_DIMENSION = 1; // 0x1
+    field @FlaggedApi("android.hardware.flags.luts_api") public static final int SAMPLING_KEY_MAX_RGB = 1; // 0x1
+    field @FlaggedApi("android.hardware.flags.luts_api") public static final int SAMPLING_KEY_RGB = 0; // 0x0
+    field @FlaggedApi("android.hardware.flags.luts_api") public static final int THREE_DIMENSION = 3; // 0x3
+  }
+
   @FlaggedApi("android.hardware.flags.overlayproperties_class_api") public final class OverlayProperties implements android.os.Parcelable {
     method @FlaggedApi("android.hardware.flags.overlayproperties_class_api") public int describeContents();
+    method @FlaggedApi("android.hardware.flags.luts_api") @NonNull public android.hardware.LutProperties[] getLutProperties();
     method @FlaggedApi("android.hardware.flags.overlayproperties_class_api") public boolean isCombinationSupported(int, int);
     method @FlaggedApi("android.hardware.flags.overlayproperties_class_api") public boolean isMixedColorSpacesSupported();
     method @FlaggedApi("android.hardware.flags.overlayproperties_class_api") public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -20628,8 +20653,14 @@
     method @NonNull public android.hardware.display.HdrConversionMode getHdrConversionMode();
     method public int getMatchContentFrameRateUserPreference();
     method public void registerDisplayListener(android.hardware.display.DisplayManager.DisplayListener, android.os.Handler);
+    method @FlaggedApi("com.android.server.display.feature.flags.display_listener_performance_improvements") public void registerDisplayListener(@NonNull java.util.concurrent.Executor, long, @NonNull android.hardware.display.DisplayManager.DisplayListener);
     method public void unregisterDisplayListener(android.hardware.display.DisplayManager.DisplayListener);
     field public static final String DISPLAY_CATEGORY_PRESENTATION = "android.hardware.display.category.PRESENTATION";
+    field @FlaggedApi("com.android.server.display.feature.flags.display_listener_performance_improvements") public static final long EVENT_FLAG_DISPLAY_ADDED = 1L; // 0x1L
+    field @FlaggedApi("com.android.server.display.feature.flags.display_listener_performance_improvements") public static final long EVENT_FLAG_DISPLAY_CHANGED = 4L; // 0x4L
+    field @FlaggedApi("com.android.server.display.feature.flags.display_listener_performance_improvements") public static final long EVENT_FLAG_DISPLAY_REFRESH_RATE = 8L; // 0x8L
+    field @FlaggedApi("com.android.server.display.feature.flags.display_listener_performance_improvements") public static final long EVENT_FLAG_DISPLAY_REMOVED = 2L; // 0x2L
+    field @FlaggedApi("com.android.server.display.feature.flags.display_listener_performance_improvements") public static final long EVENT_FLAG_DISPLAY_STATE = 16L; // 0x10L
     field public static final int MATCH_CONTENT_FRAMERATE_ALWAYS = 2; // 0x2
     field public static final int MATCH_CONTENT_FRAMERATE_NEVER = 0; // 0x0
     field public static final int MATCH_CONTENT_FRAMERATE_SEAMLESSS_ONLY = 1; // 0x1
@@ -21019,6 +21050,7 @@
     method public abstract android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodImpl onCreateInputMethodInterface();
     method public abstract android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface();
     method public boolean onGenericMotionEvent(android.view.MotionEvent);
+    method @FlaggedApi("android.view.inputmethod.verify_key_event") public boolean onShouldVerifyKeyEvent(@NonNull android.view.KeyEvent);
     method public boolean onTrackballEvent(android.view.MotionEvent);
   }
 
@@ -21036,6 +21068,7 @@
     method public void dispatchTrackballEvent(int, android.view.MotionEvent, android.view.inputmethod.InputMethodSession.EventCallback);
     method public boolean isEnabled();
     method public boolean isRevoked();
+    method @FlaggedApi("android.view.inputmethod.verify_key_event") public boolean onShouldVerifyKeyEvent(@NonNull android.view.KeyEvent);
     method public void revokeSelf();
     method public void setEnabled(boolean);
   }
@@ -21976,7 +22009,7 @@
   public final class AudioPlaybackConfiguration implements android.os.Parcelable {
     method public int describeContents();
     method public android.media.AudioAttributes getAudioAttributes();
-    method @Nullable public android.media.AudioDeviceInfo getAudioDeviceInfo();
+    method @Deprecated @FlaggedApi("android.media.audio.routed_device_ids") @Nullable public android.media.AudioDeviceInfo getAudioDeviceInfo();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.media.AudioPlaybackConfiguration> CREATOR;
   }
@@ -22059,6 +22092,7 @@
     method public android.media.AudioDeviceInfo getPreferredDevice();
     method public int getRecordingState();
     method public android.media.AudioDeviceInfo getRoutedDevice();
+    method @FlaggedApi("android.media.audio.routed_device_ids") @NonNull public java.util.List<android.media.AudioDeviceInfo> getRoutedDevices();
     method public int getSampleRate();
     method public int getState();
     method public int getTimestamp(@NonNull android.media.AudioTimestamp, int);
@@ -22153,6 +22187,7 @@
     method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
     method public android.media.AudioDeviceInfo getPreferredDevice();
     method public android.media.AudioDeviceInfo getRoutedDevice();
+    method @FlaggedApi("android.media.audio.routed_device_ids") @NonNull public default java.util.List<android.media.AudioDeviceInfo> getRoutedDevices();
     method public void removeOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener);
     method public boolean setPreferredDevice(android.media.AudioDeviceInfo);
   }
@@ -22211,6 +22246,7 @@
     method public int getPositionNotificationPeriod();
     method public android.media.AudioDeviceInfo getPreferredDevice();
     method public android.media.AudioDeviceInfo getRoutedDevice();
+    method @FlaggedApi("android.media.audio.routed_device_ids") @NonNull public java.util.List<android.media.AudioDeviceInfo> getRoutedDevices();
     method public int getSampleRate();
     method @IntRange(from=1) public int getStartThresholdInFrames();
     method public int getState();
@@ -24379,6 +24415,7 @@
     method @NonNull public android.media.PlaybackParams getPlaybackParams();
     method public android.media.AudioDeviceInfo getPreferredDevice();
     method public android.media.AudioDeviceInfo getRoutedDevice();
+    method @FlaggedApi("android.media.audio.routed_device_ids") @NonNull public java.util.List<android.media.AudioDeviceInfo> getRoutedDevices();
     method public int getSelectedTrack(int) throws java.lang.IllegalStateException;
     method @NonNull public android.media.SyncParams getSyncParams();
     method @Nullable public android.media.MediaTimestamp getTimestamp();
@@ -24592,6 +24629,7 @@
     method public android.os.PersistableBundle getMetrics();
     method public android.media.AudioDeviceInfo getPreferredDevice();
     method public android.media.AudioDeviceInfo getRoutedDevice();
+    method @FlaggedApi("android.media.audio.routed_device_ids") @NonNull public java.util.List<android.media.AudioDeviceInfo> getRoutedDevices();
     method public android.view.Surface getSurface();
     method public boolean isPrivacySensitive();
     method public void pause() throws java.lang.IllegalStateException;
@@ -28523,6 +28561,8 @@
     method public void setCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.ad.TvAdView.TvAdCallback);
     method public void setOnUnhandledInputEventListener(@NonNull android.media.tv.ad.TvAdView.OnUnhandledInputEventListener);
     method public boolean setTvView(@Nullable android.media.tv.TvView);
+    method @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public void setZOrderMediaOverlay(boolean);
+    method @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public void setZOrderOnTop(boolean);
     method public void startAdService();
     method public void stopAdService();
     field public static final String ERROR_KEY_ERROR_CODE = "error_code";
@@ -28795,6 +28835,8 @@
     method public void setOnUnhandledInputEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.interactive.TvInteractiveAppView.OnUnhandledInputEventListener);
     method public void setTeletextAppEnabled(boolean);
     method public int setTvView(@Nullable android.media.tv.TvView);
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void setZOrderMediaOverlay(boolean);
+    method @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public void setZOrderOnTop(boolean);
     method public void startInteractiveApp();
     method public void stopInteractiveApp();
     field public static final String BI_INTERACTIVE_APP_KEY_ALIAS = "alias";
@@ -33307,6 +33349,14 @@
     method public final android.os.CountDownTimer start();
   }
 
+  @FlaggedApi("android.os.cpu_gpu_headrooms") public final class CpuHeadroomParams {
+    ctor public CpuHeadroomParams();
+    method public int getCalculationType();
+    method public void setCalculationType(int);
+    field public static final int CPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1; // 0x1
+    field public static final int CPU_HEADROOM_CALCULATION_TYPE_MIN = 0; // 0x0
+  }
+
   public final class CpuUsageInfo implements android.os.Parcelable {
     method public int describeContents();
     method public long getActive();
@@ -33554,6 +33604,14 @@
     method public void onProgress(long);
   }
 
+  @FlaggedApi("android.os.cpu_gpu_headrooms") public final class GpuHeadroomParams {
+    ctor public GpuHeadroomParams();
+    method public int getCalculationType();
+    method public void setCalculationType(int);
+    field public static final int GPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1; // 0x1
+    field public static final int GPU_HEADROOM_CALCULATION_TYPE_MIN = 0; // 0x0
+  }
+
   public class Handler {
     ctor @Deprecated public Handler();
     ctor @Deprecated public Handler(@Nullable android.os.Handler.Callback);
@@ -34694,12 +34752,9 @@
     method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") public boolean areEnvelopeEffectsSupported();
     method @NonNull public boolean[] arePrimitivesSupported(@NonNull int...);
     method @RequiresPermission(android.Manifest.permission.VIBRATE) public abstract void cancel();
+    method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") @NonNull public android.os.vibrator.VibratorEnvelopeEffectInfo getEnvelopeEffectInfo();
     method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") @Nullable public android.os.vibrator.VibratorFrequencyProfile getFrequencyProfile();
     method public int getId();
-    method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") public int getMaxEnvelopeEffectControlPointDurationMillis();
-    method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") public int getMaxEnvelopeEffectDurationMillis();
-    method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") public int getMaxEnvelopeEffectSize();
-    method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") public int getMinEnvelopeEffectControlPointDurationMillis();
     method @NonNull public int[] getPrimitiveDurations(@NonNull int...);
     method public float getQFactor();
     method public float getResonantFrequency();
@@ -34807,6 +34862,10 @@
   }
 
   public class SystemHealthManager {
+    method @FlaggedApi("android.os.cpu_gpu_headrooms") @FloatRange(from=0.0f, to=100.0f) public float getCpuHeadroom(@Nullable android.os.CpuHeadroomParams);
+    method @FlaggedApi("android.os.cpu_gpu_headrooms") public long getCpuHeadroomMinIntervalMillis();
+    method @FlaggedApi("android.os.cpu_gpu_headrooms") @FloatRange(from=0.0f, to=100.0f) public float getGpuHeadroom(@Nullable android.os.GpuHeadroomParams);
+    method @FlaggedApi("android.os.cpu_gpu_headrooms") public long getGpuHeadroomMinIntervalMillis();
     method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public void getPowerMonitorReadings(@NonNull java.util.List<android.os.PowerMonitor>, @Nullable java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.os.PowerMonitorReadings,java.lang.RuntimeException>);
     method @FlaggedApi("com.android.server.power.optimization.power_monitor_api") public void getSupportedPowerMonitors(@Nullable java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<android.os.PowerMonitor>>);
     method public android.os.health.HealthStats takeMyUidSnapshot();
@@ -35050,6 +35109,16 @@
 
 package android.os.vibrator {
 
+  @FlaggedApi("android.os.vibrator.normalized_pwle_effects") public final class VibratorEnvelopeEffectInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method public long getMaxControlPointDurationMillis();
+    method public long getMaxDurationMillis();
+    method public int getMaxSize();
+    method public long getMinControlPointDurationMillis();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.VibratorEnvelopeEffectInfo> CREATOR;
+  }
+
   @FlaggedApi("android.os.vibrator.normalized_pwle_effects") public final class VibratorFrequencyProfile {
     method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") @NonNull public android.util.SparseArray<java.lang.Float> getFrequenciesOutputAcceleration();
     method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") @Nullable public android.util.Range<java.lang.Float> getFrequencyRange(float);
@@ -46659,6 +46728,8 @@
     method public long getDataUsageBytes();
     method public long getDataUsageTime();
     method @NonNull public int[] getNetworkTypes();
+    method @FlaggedApi("com.android.internal.telephony.flags.subscription_plan_allow_status_and_end_date") @Nullable public java.time.ZonedDateTime getPlanEndDate();
+    method @FlaggedApi("com.android.internal.telephony.flags.subscription_plan_allow_status_and_end_date") public int getSubscriptionStatus();
     method @Nullable public CharSequence getSummary();
     method @Nullable public CharSequence getTitle();
     method public void writeToParcel(android.os.Parcel, int);
@@ -46669,6 +46740,11 @@
     field public static final int LIMIT_BEHAVIOR_DISABLED = 0; // 0x0
     field public static final int LIMIT_BEHAVIOR_THROTTLED = 2; // 0x2
     field public static final int LIMIT_BEHAVIOR_UNKNOWN = -1; // 0xffffffff
+    field @FlaggedApi("com.android.internal.telephony.flags.subscription_plan_allow_status_and_end_date") public static final int SUBSCRIPTION_STATUS_ACTIVE = 1; // 0x1
+    field @FlaggedApi("com.android.internal.telephony.flags.subscription_plan_allow_status_and_end_date") public static final int SUBSCRIPTION_STATUS_INACTIVE = 2; // 0x2
+    field @FlaggedApi("com.android.internal.telephony.flags.subscription_plan_allow_status_and_end_date") public static final int SUBSCRIPTION_STATUS_SUSPENDED = 4; // 0x4
+    field @FlaggedApi("com.android.internal.telephony.flags.subscription_plan_allow_status_and_end_date") public static final int SUBSCRIPTION_STATUS_TRIAL = 3; // 0x3
+    field @FlaggedApi("com.android.internal.telephony.flags.subscription_plan_allow_status_and_end_date") public static final int SUBSCRIPTION_STATUS_UNKNOWN = 0; // 0x0
     field public static final long TIME_UNKNOWN = -1L; // 0xffffffffffffffffL
   }
 
@@ -46680,6 +46756,7 @@
     method public android.telephony.SubscriptionPlan.Builder setDataLimit(long, int);
     method public android.telephony.SubscriptionPlan.Builder setDataUsage(long, long);
     method @NonNull public android.telephony.SubscriptionPlan.Builder setNetworkTypes(@NonNull int[]);
+    method @FlaggedApi("com.android.internal.telephony.flags.subscription_plan_allow_status_and_end_date") @NonNull public android.telephony.SubscriptionPlan.Builder setSubscriptionStatus(int);
     method public android.telephony.SubscriptionPlan.Builder setSummary(@Nullable CharSequence);
     method public android.telephony.SubscriptionPlan.Builder setTitle(@Nullable CharSequence);
   }
@@ -53155,6 +53232,7 @@
     method @FlaggedApi("com.android.window.flags.sdk_desired_present_time") @NonNull public android.view.SurfaceControl.Transaction setFrameTimeline(long);
     method @Deprecated @NonNull public android.view.SurfaceControl.Transaction setGeometry(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect, int);
     method @NonNull public android.view.SurfaceControl.Transaction setLayer(@NonNull android.view.SurfaceControl, @IntRange(from=java.lang.Integer.MIN_VALUE, to=java.lang.Integer.MAX_VALUE) int);
+    method @FlaggedApi("android.hardware.flags.luts_api") @NonNull public android.view.SurfaceControl.Transaction setLuts(@NonNull android.view.SurfaceControl, @Nullable android.hardware.DisplayLuts);
     method @NonNull public android.view.SurfaceControl.Transaction setOpaque(@NonNull android.view.SurfaceControl, boolean);
     method @NonNull public android.view.SurfaceControl.Transaction setPosition(@NonNull android.view.SurfaceControl, float, float);
     method @NonNull public android.view.SurfaceControl.Transaction setScale(@NonNull android.view.SurfaceControl, float, float);
@@ -56947,6 +57025,7 @@
     method @NonNull public java.util.Set<java.lang.Class<? extends android.view.inputmethod.PreviewableHandwritingGesture>> getSupportedHandwritingGesturePreviews();
     method @NonNull public java.util.List<java.lang.Class<? extends android.view.inputmethod.HandwritingGesture>> getSupportedHandwritingGestures();
     method @FlaggedApi("android.view.inputmethod.editorinfo_handwriting_enabled") public boolean isStylusHandwritingEnabled();
+    method @FlaggedApi("android.view.inputmethod.writing_tools") public boolean isWritingToolsEnabled();
     method public final void makeCompatible(int);
     method @FlaggedApi("android.view.inputmethod.public_autofill_id_in_editorinfo") public void setAutofillId(@Nullable android.view.autofill.AutofillId);
     method public void setInitialSurroundingSubText(@NonNull CharSequence, int);
@@ -56955,6 +57034,7 @@
     method @FlaggedApi("android.view.inputmethod.editorinfo_handwriting_enabled") public void setStylusHandwritingEnabled(boolean);
     method public void setSupportedHandwritingGesturePreviews(@NonNull java.util.Set<java.lang.Class<? extends android.view.inputmethod.PreviewableHandwritingGesture>>);
     method public void setSupportedHandwritingGestures(@NonNull java.util.List<java.lang.Class<? extends android.view.inputmethod.HandwritingGesture>>);
+    method @FlaggedApi("android.view.inputmethod.writing_tools") public void setWritingToolsEnabled(boolean);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.EditorInfo> CREATOR;
     field public static final int IME_ACTION_DONE = 6; // 0x6
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index ed95fdd..fee53d6 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -7464,6 +7464,7 @@
   }
 
   public final class AudioPlaybackConfiguration implements android.os.Parcelable {
+    method @FlaggedApi("android.media.audio.routed_device_ids") @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDeviceInfo> getAudioDeviceInfos();
     method public int getChannelMask();
     method public int getClientPid();
     method public int getClientUid();
@@ -7618,7 +7619,7 @@
 
   public final class MediaCas implements java.lang.AutoCloseable {
     method @FlaggedApi("android.media.tv.flags.set_resource_holder_retain") @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public void setResourceHolderRetain(boolean);
-    method @FlaggedApi("com.android.media.flags.update_client_profile_priority") @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public boolean updateResourcePriority(int, int);
+    method @FlaggedApi("android.media.tv.flags.mediacas_update_client_profile_priority") @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public boolean updateResourcePriority(int, int);
   }
 
   public final class MediaCodec {
diff --git a/core/java/Android.bp b/core/java/Android.bp
index cf5ebbaa3..bc38294 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -232,6 +232,8 @@
         "android.hardware.power-aidl",
     ],
     srcs: [
+        "android/os/CpuHeadroomParamsInternal.aidl",
+        "android/os/GpuHeadroomParamsInternal.aidl",
         "android/os/IHintManager.aidl",
         "android/os/IHintSession.aidl",
     ],
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index e451116..ee0c38c 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -190,6 +190,7 @@
 import android.os.IBinder;
 import android.os.IDumpstate;
 import android.os.IHardwarePropertiesManager;
+import android.os.IHintManager;
 import android.os.IPowerManager;
 import android.os.IPowerStatsService;
 import android.os.IRecoverySystem;
@@ -1195,8 +1196,10 @@
             public SystemHealthManager createService(ContextImpl ctx) throws ServiceNotFoundException {
                 IBinder batteryStats = ServiceManager.getServiceOrThrow(BatteryStats.SERVICE_NAME);
                 IBinder powerStats = ServiceManager.getService(Context.POWER_STATS_SERVICE);
+                IBinder perfHint = ServiceManager.getService(Context.PERFORMANCE_HINT_SERVICE);
                 return new SystemHealthManager(IBatteryStats.Stub.asInterface(batteryStats),
-                        IPowerStatsService.Stub.asInterface(powerStats));
+                        IPowerStatsService.Stub.asInterface(powerStats),
+                        IHintManager.Stub.asInterface(perfHint));
             }});
 
         registerService(Context.CONTEXTHUB_SERVICE, ContextHubManager.class,
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index c789c41..4e68b5a 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -12196,6 +12196,33 @@
     }
 
     /**
+     * Adds a user restriction globally, specified by the {@code key}.
+     *
+     * <p>Called by a system service only, meaning that the caller's UID must be equal to
+     * {@link Process#SYSTEM_UID}.
+     *
+     * @param systemEntity The service entity that adds the restriction. A user restriction set by
+     *                     a service entity can only be cleared by the same entity. This can be
+     *                     just the calling package name, or any string of the caller's choice
+     *                     can be used.
+     * @param key The key of the restriction.
+     * @throws SecurityException if the caller is not a system service.
+     *
+     * @hide
+     */
+    public void addUserRestrictionGlobally(@NonNull String systemEntity,
+            @NonNull @UserManager.UserRestrictionKey String key) {
+        if (mService != null) {
+            try {
+                mService.setUserRestrictionGloballyFromSystem(systemEntity, key,
+                        /* enable= */ true);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
      * Called by a profile owner, device owner or a holder of any permission that is associated with
      * a user restriction to clear a user restriction specified by the key.
      * <p>
@@ -12281,6 +12308,33 @@
     }
 
     /**
+     * Clears a user restriction globally, specified by the {@code key}.
+     *
+     * <p>Called by a system service only, meaning that the caller's UID must be equal to
+     * {@link Process#SYSTEM_UID}.
+     *
+     * @param systemEntity The system entity that clears the restriction. A user restriction
+     *                     set by a system entity can only be cleared by the same entity. This
+     *                     can be just the calling package name, or any string of the caller's
+     *                     choice can be used.
+     * @param key The key of the restriction.
+     * @throws SecurityException if the caller is not a system service.
+     *
+     * @hide
+     */
+    public void clearUserRestrictionGlobally(@NonNull String systemEntity,
+            @NonNull @UserManager.UserRestrictionKey String key) {
+        if (mService != null) {
+            try {
+                mService.setUserRestrictionGloballyFromSystem(systemEntity, key,
+                        /* enable= */ false);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
      * Called by an admin to get user restrictions set by themselves with
      * {@link #addUserRestriction(ComponentName, String)}.
      * <p>
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index a406802..fa984af 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -257,6 +257,7 @@
     void setUserRestriction(in ComponentName who, in String callerPackage, in String key, boolean enable, boolean parent);
     void setUserRestrictionForUser(in String systemEntity, in String key, boolean enable, int targetUser);
     void setUserRestrictionGlobally(in String callerPackage, in String key);
+    void setUserRestrictionGloballyFromSystem(in String systemEntity, in String key, boolean enable);
     Bundle getUserRestrictions(in ComponentName who, in String callerPackage, boolean parent);
     Bundle getUserRestrictionsGlobally(in String callerPackage);
 
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 9ba5a35..e181ae8 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -366,3 +366,13 @@
     description: "Block app installations that specify an incompatible minor SDK version"
     bug: "377474232"
 }
+
+flag {
+    name: "app_compat_option_16kb"
+    is_exported: true
+    namespace: "devoptions_settings"
+    description: "Feature flag to enable page size app compat mode from manifest, package manager and settings level."
+    bug: "371049373"
+    is_fixed_read_only: true
+}
+
diff --git a/core/java/android/hardware/DisplayLuts.java b/core/java/android/hardware/DisplayLuts.java
index b162ad6..6343ba1 100644
--- a/core/java/android/hardware/DisplayLuts.java
+++ b/core/java/android/hardware/DisplayLuts.java
@@ -16,116 +16,294 @@
 
 package android.hardware;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
+import android.hardware.flags.Flags;
 import android.util.IntArray;
 
 import java.util.ArrayList;
-import java.util.List;
 
 /**
- * @hide
+ * DisplayLuts provides the developers to apply Lookup Tables (Luts) to a
+ * {@link android.view.SurfaceControl}. Luts provides ways to control tonemapping
+ * for specific content.
+ *
+ * The general flow is as follows:
+ * <p>
+ *      <img src="{@docRoot}reference/android/images/graphics/DisplayLuts.png" />
+ *      <figcaption style="text-align: center;">DisplayLuts flow</figcaption>
+ * </p>
+ *
+ * @see LutProperties
  */
+@FlaggedApi(Flags.FLAG_LUTS_API)
 public final class DisplayLuts {
+    private ArrayList<Entry> mEntries;
     private IntArray mOffsets;
     private int mTotalLength;
 
-    private List<float[]> mLutBuffers;
-    private IntArray mLutDimensions;
-    private IntArray mLutSizes;
-    private IntArray mLutSamplingKeys;
-    private static final int LUT_LENGTH_LIMIT = 100000;
-
+    /**
+     * Create a {@link DisplayLuts} instance.
+     */
+    @FlaggedApi(Flags.FLAG_LUTS_API)
     public DisplayLuts() {
+        mEntries = new ArrayList<>();
         mOffsets = new IntArray();
         mTotalLength = 0;
-
-        mLutBuffers = new ArrayList<>();
-        mLutDimensions = new IntArray();
-        mLutSizes = new IntArray();
-        mLutSamplingKeys = new IntArray();
     }
 
-    /**
-     * Add the lut to be applied.
-     *
-     * @param buffer
-     * @param dimension either 1D or 3D
-     * @param size
-     * @param samplingKey
-     */
-    public void addLut(@NonNull float[] buffer, @LutProperties.Dimension int dimension,
-                       int size, @LutProperties.SamplingKey int samplingKey) {
+    @FlaggedApi(Flags.FLAG_LUTS_API)
+    public static class Entry {
+        private float[] mBuffer;
+        private @LutProperties.Dimension int mDimension;
+        private int mSize;
+        private @LutProperties.SamplingKey int mSamplingKey;
 
-        int lutLength = 0;
-        if (dimension == LutProperties.ONE_DIMENSION) {
-            lutLength = size;
-        } else if (dimension == LutProperties.THREE_DIMENSION) {
-            lutLength = size * size * size;
-        } else {
-            clear();
-            throw new IllegalArgumentException("The dimension is either 1D or 3D!");
+        private static final int LUT_LENGTH_LIMIT = 100000;
+
+        /**
+         * Create a Lut entry.
+         *
+         * <p>
+         * Noted that 1D Lut(s) are treated as gain curves.
+         * For 3D Lut(s), 3D Lut(s) are used for direct color manipulations.
+         * The values of 3D Lut(s) data should be normalized to the range {@code 0.0}
+         * to {@code 1.0}, inclusive. And 3D Lut(s) data is organized in the order of
+         * R, G, B channels.
+         *
+         * @param buffer The raw lut data
+         * @param dimension Either 1D or 3D
+         * @param samplingKey The sampling kay used for the Lut
+         */
+        @FlaggedApi(Flags.FLAG_LUTS_API)
+        public Entry(@NonNull float[] buffer,
+                    @LutProperties.Dimension int dimension,
+                    @LutProperties.SamplingKey int samplingKey) {
+            if (buffer == null || buffer.length < 1) {
+                throw new IllegalArgumentException("The buffer cannot be empty!");
+            }
+
+            if (buffer.length >= LUT_LENGTH_LIMIT) {
+                throw new IllegalArgumentException("The lut length is too big to handle!");
+            }
+
+            if (dimension != LutProperties.ONE_DIMENSION
+                    && dimension != LutProperties.THREE_DIMENSION) {
+                throw new IllegalArgumentException("The dimension should be either 1D or 3D!");
+            }
+
+            if (dimension == LutProperties.THREE_DIMENSION) {
+                if (buffer.length <= 3) {
+                    throw new IllegalArgumentException(
+                            "The 3d lut size of each dimension should be over 1!");
+                }
+                int lengthPerChannel = buffer.length;
+                if (lengthPerChannel % 3 != 0) {
+                    throw new IllegalArgumentException(
+                            "The lut buffer of 3dlut should have 3 channels!");
+                }
+                lengthPerChannel /= 3;
+
+                double size = Math.cbrt(lengthPerChannel);
+                if (size == (int) size) {
+                    mSize = (int) size;
+                } else {
+                    throw new IllegalArgumentException(
+                            "Cannot get the cube root of the 3d lut buffer!");
+                }
+            } else {
+                mSize = buffer.length;
+            }
+
+            mBuffer = buffer;
+            mDimension = dimension;
+            mSamplingKey = samplingKey;
         }
 
-        if (lutLength >= LUT_LENGTH_LIMIT) {
-            clear();
-            throw new IllegalArgumentException("The lut length is too big to handle!");
+        /**
+         * @return the dimension of the lut entry
+         */
+        @FlaggedApi(Flags.FLAG_LUTS_API)
+        public int getDimension() {
+            return mDimension;
         }
 
+        /**
+         * @return the size of the lut for each dimension
+         * @hide
+         */
+        public int getSize() {
+            return mSize;
+        }
+
+        /**
+         * @return the lut raw data of the lut
+         */
+        @FlaggedApi(Flags.FLAG_LUTS_API)
+        public @NonNull float[] getBuffer() {
+            return mBuffer;
+        }
+
+        /**
+         * @return the sampling key used by the lut
+         */
+        @FlaggedApi(Flags.FLAG_LUTS_API)
+        public int getSamplingKey() {
+            return mSamplingKey;
+        }
+
+        @Override
+        public String toString() {
+            return "Entry{"
+                    + "dimension=" + DisplayLuts.Entry.dimensionToString(getDimension())
+                    + ", size(each dimension)=" + getSize()
+                    + ", samplingKey=" + samplingKeyToString(getSamplingKey()) + "}";
+        }
+
+        private static String dimensionToString(int dimension) {
+            switch(dimension) {
+                case LutProperties.ONE_DIMENSION:
+                    return "ONE_DIMENSION";
+                case LutProperties.THREE_DIMENSION:
+                    return "THREE_DIMENSION";
+                default:
+                    return "";
+            }
+        }
+
+        private static String samplingKeyToString(int key) {
+            switch(key) {
+                case LutProperties.SAMPLING_KEY_RGB:
+                    return "SAMPLING_KEY_RGB";
+                case LutProperties.SAMPLING_KEY_MAX_RGB:
+                    return "SAMPLING_KEY_MAX_RGB";
+                default:
+                    return "";
+            }
+        }
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder("DisplayLuts{");
+        sb.append("\n");
+        for (DisplayLuts.Entry entry: mEntries) {
+            sb.append(entry.toString());
+            sb.append("\n");
+        }
+        sb.append("}");
+        return sb.toString();
+    }
+
+    private void addEntry(Entry entry) {
+        mEntries.add(entry);
         mOffsets.add(mTotalLength);
-        mTotalLength += lutLength;
-
-        mLutBuffers.add(buffer);
-        mLutDimensions.add(dimension);
-        mLutSizes.add(size);
-        mLutSamplingKeys.add(samplingKey);
+        mTotalLength += entry.getBuffer().length;
     }
 
     private void clear() {
-        mTotalLength = 0;
         mOffsets.clear();
-        mLutBuffers.clear();
-        mLutDimensions.clear();
-        mLutSamplingKeys.clear();
+        mTotalLength = 0;
+        mEntries.clear();
     }
 
     /**
-     * @return the array of Lut buffers
+     * Set a Lut to be applied.
+     *
+     * <p>Use either this or {@link #set(Entry, Entry)}. The function will
+     * replace any previously set lut(s).</p>
+     *
+     * @param entry Either an 1D Lut or a 3D Lut
+     */
+    @FlaggedApi(Flags.FLAG_LUTS_API)
+    public void set(@NonNull Entry entry) {
+        if (entry == null) {
+            throw new IllegalArgumentException("The entry is null!");
+        }
+        clear();
+        addEntry(entry);
+    }
+
+    /**
+     * Set Luts in order to be applied.
+     *
+     * <p> An 1D Lut and 3D Lut will be applied in order. Use either this or
+     * {@link #set(Entry)}. The function will replace any previously set lut(s)</p>
+     *
+     * @param first An 1D Lut
+     * @param second A 3D Lut
+     */
+    @FlaggedApi(Flags.FLAG_LUTS_API)
+    public void set(@NonNull Entry first, @NonNull Entry second) {
+        if (first == null || second == null) {
+            throw new IllegalArgumentException("The entry is null!");
+        }
+        if (first.getDimension() != LutProperties.ONE_DIMENSION
+                || second.getDimension() != LutProperties.THREE_DIMENSION) {
+            throw new IllegalArgumentException("The entries should be 1D and 3D in order!");
+        }
+        clear();
+        addEntry(first);
+        addEntry(second);
+    }
+
+    /**
+     * @hide
+     */
+    public boolean valid() {
+        return mEntries.size() > 0;
+    }
+
+    /**
+     * @hide
      */
     public float[] getLutBuffers() {
         float[] buffer = new float[mTotalLength];
 
-        for (int i = 0; i < mLutBuffers.size(); i++) {
-            float[] lutBuffer = mLutBuffers.get(i);
+        for (int i = 0; i < mEntries.size(); i++) {
+            float[] lutBuffer = mEntries.get(i).getBuffer();
             System.arraycopy(lutBuffer, 0, buffer, mOffsets.get(i), lutBuffer.length);
         }
         return buffer;
     }
 
     /**
-     * @return the starting point of each lut memory region of the lut buffer
+     * @hide
      */
     public int[] getOffsets() {
         return mOffsets.toArray();
     }
 
     /**
-     * @return the array of Lut size
+     * @hide
      */
     public int[] getLutSizes() {
-        return mLutSizes.toArray();
+        int[] sizes = new int[mEntries.size()];
+        for (int i = 0; i < mEntries.size(); i++) {
+            sizes[i] = mEntries.get(i).getSize();
+        }
+        return sizes;
     }
 
     /**
-     * @return the array of Lut dimension
+     * @hide
      */
     public int[] getLutDimensions() {
-        return mLutDimensions.toArray();
+        int[] dimensions = new int[mEntries.size()];
+        for (int i = 0; i < mEntries.size(); i++) {
+            dimensions[i] = mEntries.get(i).getDimension();
+        }
+        return dimensions;
     }
 
     /**
-     * @return the array of sampling key
+     * @hide
      */
     public int[] getLutSamplingKeys() {
-        return mLutSamplingKeys.toArray();
+        int[] samplingKeys = new int[mEntries.size()];
+        for (int i = 0; i < mEntries.size(); i++) {
+            samplingKeys[i] = mEntries.get(i).getSamplingKey();
+        }
+        return samplingKeys;
     }
 }
diff --git a/core/java/android/hardware/LutProperties.java b/core/java/android/hardware/LutProperties.java
index c9c6d6d..bf40a41 100644
--- a/core/java/android/hardware/LutProperties.java
+++ b/core/java/android/hardware/LutProperties.java
@@ -16,23 +16,31 @@
 
 package android.hardware;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.hardware.flags.Flags;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
 /**
- * Lut properties class.
+ * Provides Lut properties of the device.
  *
- * A Lut (Look-Up Table) is a pre-calculated table for color transformation.
- *
- * @hide
+ * <p>
+ * A Lut (Look-Up Table) is a pre-calculated table for color correction.
+ * Applications may be interested in the Lut properties exposed by
+ * this class to determine if the Lut(s) they select using
+ * {@link android.view.SurfaceControl.Transaction#setLuts} are by the HWC.
+ * </p>
  */
+@FlaggedApi(Flags.FLAG_LUTS_API)
 public final class LutProperties {
     private final @Dimension int mDimension;
     private final int mSize;
     private final @SamplingKey int[] mSamplingKeys;
 
+    /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"SAMPLING_KEY_"}, value = {
         SAMPLING_KEY_RGB,
@@ -42,11 +50,14 @@
     }
 
     /** use r,g,b channel as the gain value of a Lut */
+    @FlaggedApi(Flags.FLAG_LUTS_API)
     public static final int SAMPLING_KEY_RGB = 0;
 
     /** use max of r,g,b channel as the gain value of a Lut */
+    @FlaggedApi(Flags.FLAG_LUTS_API)
     public static final int SAMPLING_KEY_MAX_RGB = 1;
 
+    /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(value = {
         ONE_DIMENSION,
@@ -56,18 +67,22 @@
     }
 
     /** The Lut is one dimensional */
+    @FlaggedApi(Flags.FLAG_LUTS_API)
     public static final int ONE_DIMENSION = 1;
 
     /** The Lut is three dimensional */
+    @FlaggedApi(Flags.FLAG_LUTS_API)
     public static final int THREE_DIMENSION = 3;
 
+    @FlaggedApi(Flags.FLAG_LUTS_API)
     public @Dimension int getDimension() {
         return mDimension;
     }
 
     /**
-     * @return the size of the Lut.
+     * @return the size of the Lut for each dimension
      */
+    @FlaggedApi(Flags.FLAG_LUTS_API)
     public int getSize() {
         return mSize;
     }
@@ -75,6 +90,8 @@
     /**
      * @return the list of sampling keys
      */
+    @FlaggedApi(Flags.FLAG_LUTS_API)
+    @NonNull
     public @SamplingKey int[] getSamplingKeys() {
         if (mSamplingKeys.length == 0) {
             throw new IllegalStateException("no sampling key!");
diff --git a/core/java/android/hardware/OverlayProperties.java b/core/java/android/hardware/OverlayProperties.java
index 24cfc1b..d42bfae 100644
--- a/core/java/android/hardware/OverlayProperties.java
+++ b/core/java/android/hardware/OverlayProperties.java
@@ -18,6 +18,7 @@
 
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
+import android.annotation.SuppressLint;
 import android.hardware.flags.Flags;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -72,9 +73,11 @@
     }
 
     /**
-     * Gets the lut properties of the display.
-     * @hide
+     * Returns the lut properties of the device.
      */
+    @FlaggedApi(Flags.FLAG_LUTS_API)
+    @SuppressLint("ArrayReturn")
+    @NonNull
     public LutProperties[] getLutProperties() {
         if (mNativeObject == 0) {
             return null;
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 28da644..e6a1640 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -21,6 +21,8 @@
 import static android.view.Display.HdrCapabilities.HdrType;
 import static android.view.Display.INVALID_DISPLAY;
 
+import static com.android.server.display.feature.flags.Flags.FLAG_DISPLAY_LISTENER_PERFORMANCE_IMPROVEMENTS;
+
 import android.Manifest;
 import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
@@ -576,6 +578,8 @@
             EVENT_FLAG_DISPLAY_ADDED,
             EVENT_FLAG_DISPLAY_CHANGED,
             EVENT_FLAG_DISPLAY_REMOVED,
+            EVENT_FLAG_DISPLAY_REFRESH_RATE,
+            EVENT_FLAG_DISPLAY_STATE
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface EventFlag {}
@@ -596,8 +600,8 @@
      *
      * @see #registerDisplayListener(DisplayListener, Handler, long)
      *
-     * @hide
      */
+    @FlaggedApi(FLAG_DISPLAY_LISTENER_PERFORMANCE_IMPROVEMENTS)
     public static final long EVENT_FLAG_DISPLAY_ADDED = 1L << 0;
 
     /**
@@ -605,8 +609,8 @@
      *
      * @see #registerDisplayListener(DisplayListener, Handler, long)
      *
-     * @hide
      */
+    @FlaggedApi(FLAG_DISPLAY_LISTENER_PERFORMANCE_IMPROVEMENTS)
     public static final long EVENT_FLAG_DISPLAY_REMOVED = 1L << 1;
 
     /**
@@ -614,10 +618,27 @@
      *
      * @see #registerDisplayListener(DisplayListener, Handler, long)
      *
-     * @hide
      */
+    @FlaggedApi(FLAG_DISPLAY_LISTENER_PERFORMANCE_IMPROVEMENTS)
     public static final long EVENT_FLAG_DISPLAY_CHANGED = 1L << 2;
 
+
+    /**
+     * Event flag to register for a display's refresh rate changes.
+     *
+     * @see #registerDisplayListener(DisplayListener, Handler, long)
+     */
+    @FlaggedApi(FLAG_DISPLAY_LISTENER_PERFORMANCE_IMPROVEMENTS)
+    public static final long EVENT_FLAG_DISPLAY_REFRESH_RATE = 1L << 3;
+
+    /**
+     * Event flag to register for a display state changes.
+     *
+     * @see #registerDisplayListener(DisplayListener, Handler, long)
+     */
+    @FlaggedApi(FLAG_DISPLAY_LISTENER_PERFORMANCE_IMPROVEMENTS)
+    public static final long EVENT_FLAG_DISPLAY_STATE = 1L << 4;
+
     /**
      * Event flag to register for a display's brightness changes. This notification is sent
      * through the {@link DisplayListener#onDisplayChanged} callback method. New brightness
@@ -787,9 +808,6 @@
      * if the listener should be invoked on the calling thread's looper.
      * @param eventFlags A bitmask of the event types for which this listener is subscribed.
      *
-     * @see #EVENT_FLAG_DISPLAY_ADDED
-     * @see #EVENT_FLAG_DISPLAY_CHANGED
-     * @see #EVENT_FLAG_DISPLAY_REMOVED
      * @see #registerDisplayListener(DisplayListener, Handler)
      * @see #unregisterDisplayListener
      *
@@ -806,18 +824,31 @@
      * Registers a display listener to receive notifications about given display event types.
      *
      * @param listener The listener to register.
+     * @param executor Executor for the thread that will be receiving the callbacks. Cannot be null.
+     * @param eventFlags A bitmask of the event types for which this listener is subscribed.
+     *
+     * @see #registerDisplayListener(DisplayListener, Handler)
+     * @see #unregisterDisplayListener
+     *
+     */
+    @FlaggedApi(FLAG_DISPLAY_LISTENER_PERFORMANCE_IMPROVEMENTS)
+    public void registerDisplayListener(@NonNull Executor executor, @EventFlag long eventFlags,
+            @NonNull DisplayListener listener) {
+        mGlobal.registerDisplayListener(listener, executor,
+                mGlobal.mapFlagsToInternalEventFlag(eventFlags, 0),
+                ActivityThread.currentPackageName());
+    }
+
+    /**
+     * Registers a display listener to receive notifications about given display event types.
+     *
+     * @param listener The listener to register.
      * @param handler The handler on which the listener should be invoked, or null
      * if the listener should be invoked on the calling thread's looper.
      * @param eventFlags A bitmask of the event types for which this listener is subscribed.
      * @param privateEventFlags A bitmask of the private event types for which this listener
      *                          is subscribed.
      *
-     * @see #EVENT_FLAG_DISPLAY_ADDED
-     * @see #EVENT_FLAG_DISPLAY_CHANGED
-     * @see #EVENT_FLAG_DISPLAY_REMOVED
-     * @see #PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS
-     * @see #PRIVATE_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED
-     * @see #PRIVATE_EVENT_FLAG_HDR_SDR_RATIO_CHANGED
      * @see #registerDisplayListener(DisplayListener, Handler)
      * @see #unregisterDisplayListener
      *
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 03b44f6..be710b1 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -62,6 +62,7 @@
 import android.view.Surface;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.feature.flags.Flags;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -108,6 +109,8 @@
             EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED,
             EVENT_DISPLAY_CONNECTED,
             EVENT_DISPLAY_DISCONNECTED,
+            EVENT_DISPLAY_REFRESH_RATE_CHANGED,
+            EVENT_DISPLAY_STATE_CHANGED
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface DisplayEvent {}
@@ -119,6 +122,8 @@
     public static final int EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED = 5;
     public static final int EVENT_DISPLAY_CONNECTED = 6;
     public static final int EVENT_DISPLAY_DISCONNECTED = 7;
+    public static final int EVENT_DISPLAY_REFRESH_RATE_CHANGED = 8;
+    public static final int EVENT_DISPLAY_STATE_CHANGED = 9;
 
     @LongDef(prefix = {"INTERNAL_EVENT_DISPLAY"}, flag = true, value = {
             INTERNAL_EVENT_FLAG_DISPLAY_ADDED,
@@ -127,6 +132,8 @@
             INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED,
             INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED,
             INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED,
+            INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE,
+            INTERNAL_EVENT_FLAG_DISPLAY_STATE
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface InternalEventFlag {}
@@ -137,6 +144,8 @@
     public static final long INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED = 1L << 3;
     public static final long INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED = 1L << 4;
     public static final long INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED = 1L << 5;
+    public static final long INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE = 1L << 6;
+    public static final long INTERNAL_EVENT_FLAG_DISPLAY_STATE = 1L << 7;
 
     @UnsupportedAppUsage
     private static DisplayManagerGlobal sInstance;
@@ -1427,6 +1436,18 @@
                         mListener.onDisplayDisconnected(displayId);
                     }
                     break;
+                case EVENT_DISPLAY_REFRESH_RATE_CHANGED:
+                    if ((mInternalEventFlagsMask
+                            & INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE) != 0) {
+                        mListener.onDisplayChanged(displayId);
+                    }
+                    break;
+                case EVENT_DISPLAY_STATE_CHANGED:
+                    if ((mInternalEventFlagsMask
+                            & INTERNAL_EVENT_FLAG_DISPLAY_STATE) != 0) {
+                        mListener.onDisplayChanged(displayId);
+                    }
+                    break;
             }
             if (DEBUG) {
                 Trace.endSection();
@@ -1566,6 +1587,10 @@
                 return "EVENT_DISPLAY_CONNECTED";
             case EVENT_DISPLAY_DISCONNECTED:
                 return "EVENT_DISPLAY_DISCONNECTED";
+            case EVENT_DISPLAY_REFRESH_RATE_CHANGED:
+                return "EVENT_DISPLAY_REFRESH_RATE_CHANGED";
+            case EVENT_DISPLAY_STATE_CHANGED:
+                return "EVENT_DISPLAY_STATE_CHANGED";
         }
         return "UNKNOWN";
     }
@@ -1630,6 +1655,17 @@
             baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_REMOVED;
         }
 
+        if (Flags.displayListenerPerformanceImprovements()) {
+            if ((eventFlags & DisplayManager.EVENT_FLAG_DISPLAY_REFRESH_RATE) != 0) {
+                baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE;
+            }
+
+            if ((eventFlags & DisplayManager.EVENT_FLAG_DISPLAY_STATE) != 0) {
+                baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_STATE;
+            }
+        }
+
+
         return baseEventMask;
     }
 }
diff --git a/core/java/android/hardware/flags/overlayproperties_flags.aconfig b/core/java/android/hardware/flags/flags.aconfig
similarity index 63%
rename from core/java/android/hardware/flags/overlayproperties_flags.aconfig
rename to core/java/android/hardware/flags/flags.aconfig
index 6c86108..5ca6c6b 100644
--- a/core/java/android/hardware/flags/overlayproperties_flags.aconfig
+++ b/core/java/android/hardware/flags/flags.aconfig
@@ -2,6 +2,15 @@
 container: "system"
 
 flag {
+    name: "luts_api"
+    is_exported: true
+    is_fixed_read_only: true
+    namespace: "core_graphics"
+    description: "public Luts related Apis"
+    bug: "349667978"
+}
+
+flag {
     name: "overlayproperties_class_api"
     is_exported: true
     namespace: "core_graphics"
diff --git a/core/java/android/hardware/input/AidlInputGestureData.aidl b/core/java/android/hardware/input/AidlInputGestureData.aidl
index e33ec53..f7410d2 100644
--- a/core/java/android/hardware/input/AidlInputGestureData.aidl
+++ b/core/java/android/hardware/input/AidlInputGestureData.aidl
@@ -28,15 +28,18 @@
     String appLaunchPackageName;
     String appLaunchClassName;
 
+    @JavaDerive(equals=true)
     parcelable KeyTrigger {
         int keycode;
         int modifierState;
     }
 
+    @JavaDerive(equals=true)
     parcelable TouchpadGestureTrigger {
         int gestureType;
     }
 
+    @JavaDerive(equals=true)
     union Trigger {
         KeyTrigger key;
         TouchpadGestureTrigger touchpadGesture;
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 4b2f2c2..fee0749 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -170,4 +170,11 @@
   namespace: "input"
   description: "Adds key gestures for talkback and magnifier"
   bug: "375277034"
-}
\ No newline at end of file
+}
+
+flag {
+  name: "can_window_override_power_gesture_api"
+  namespace: "wallet_integration"
+  description: "Adds new API in WindowManager class to check if the window can override the power key double tap behavior."
+  bug: "378736024"
+  }
\ No newline at end of file
diff --git a/core/java/android/inputmethodservice/AbstractInputMethodService.java b/core/java/android/inputmethodservice/AbstractInputMethodService.java
index 4bc5bd2..26308f6 100644
--- a/core/java/android/inputmethodservice/AbstractInputMethodService.java
+++ b/core/java/android/inputmethodservice/AbstractInputMethodService.java
@@ -16,6 +16,9 @@
 
 package android.inputmethodservice;
 
+import static android.view.inputmethod.Flags.FLAG_VERIFY_KEY_EVENT;
+
+import android.annotation.FlaggedApi;
 import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -193,6 +196,12 @@
             }
         }
 
+        @FlaggedApi(FLAG_VERIFY_KEY_EVENT)
+        @Override
+        public boolean onShouldVerifyKeyEvent(@NonNull KeyEvent event) {
+            return AbstractInputMethodService.this.onShouldVerifyKeyEvent(event);
+        }
+
         /**
          * Take care of dispatching incoming trackball events to the appropriate
          * callbacks on the service, and tell the client when this is done.
@@ -308,6 +317,14 @@
         return false;
     }
 
+    /**
+     * @see InputMethodService#onShouldVerifyKeyEvent(KeyEvent)
+     */
+    @FlaggedApi(FLAG_VERIFY_KEY_EVENT)
+    public boolean onShouldVerifyKeyEvent(@NonNull KeyEvent event) {
+        return false;
+    }
+
     /** @hide */
     @Override
     public final int getWindowType() {
diff --git a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
index 62b131a..9b37533 100644
--- a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
@@ -16,12 +16,16 @@
 
 package android.inputmethodservice;
 
+import static android.view.inputmethod.Flags.verifyKeyEvent;
+
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.graphics.Rect;
+import android.hardware.input.InputManager;
 import android.os.Bundle;
 import android.os.Looper;
 import android.os.Message;
+import android.os.SystemClock;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.InputChannel;
@@ -41,6 +45,8 @@
 import com.android.internal.os.HandlerCaller;
 import com.android.internal.os.SomeArgs;
 
+import java.util.Objects;
+
 class IInputMethodSessionWrapper extends IInputMethodSession.Stub
         implements HandlerCaller.Callback {
     private static final String TAG = "InputMethodWrapper";
@@ -56,6 +62,7 @@
     private static final int DO_REMOVE_IME_SURFACE = 130;
     private static final int DO_FINISH_INPUT = 140;
     private static final int DO_INVALIDATE_INPUT = 150;
+    private final Context mContext;
 
 
     @UnsupportedAppUsage
@@ -66,6 +73,7 @@
 
     public IInputMethodSessionWrapper(Context context,
             InputMethodSession inputMethodSession, InputChannel channel) {
+        mContext = context;
         mCaller = new HandlerCaller(context, null,
                 this, true /*asyncHandler*/);
         mInputMethodSession = inputMethodSession;
@@ -233,6 +241,8 @@
     }
     private final class ImeInputEventReceiver extends InputEventReceiver
             implements InputMethodSession.EventCallback {
+        // Time after which a KeyEvent is invalid
+        private static final long KEY_EVENT_ALLOW_PERIOD_MS = 100L;
         private final SparseArray<InputEvent> mPendingEvents = new SparseArray<InputEvent>();
 
         public ImeInputEventReceiver(InputChannel inputChannel, Looper looper) {
@@ -247,10 +257,23 @@
                 return;
             }
 
+            if (event instanceof KeyEvent keyEvent && needsVerification(keyEvent)) {
+                // any KeyEvent with modifiers (e.g. Ctrl/Alt/Fn) must be verified that
+                // they originated from system.
+                InputManager im = mContext.getSystemService(InputManager.class);
+                Objects.requireNonNull(im);
+                final long age = SystemClock.uptimeMillis() - keyEvent.getEventTime();
+                if (age >= KEY_EVENT_ALLOW_PERIOD_MS && im.verifyInputEvent(keyEvent) == null) {
+                    Log.w(TAG, "Unverified or Invalid KeyEvent injected into IME. Dropping "
+                            + keyEvent);
+                    finishInputEvent(event, false /* handled */);
+                    return;
+                }
+            }
+
             final int seq = event.getSequenceNumber();
             mPendingEvents.put(seq, event);
-            if (event instanceof KeyEvent) {
-                KeyEvent keyEvent = (KeyEvent)event;
+            if (event instanceof KeyEvent keyEvent) {
                 mInputMethodSession.dispatchKeyEvent(seq, keyEvent, this);
             } else {
                 MotionEvent motionEvent = (MotionEvent)event;
@@ -271,5 +294,21 @@
                 finishInputEvent(event, handled);
             }
         }
+
+        private boolean hasKeyModifiers(KeyEvent event) {
+            if (event.hasNoModifiers()) {
+                return false;
+            }
+            return event.hasModifiers(KeyEvent.META_CTRL_ON)
+                    || event.hasModifiers(KeyEvent.META_ALT_ON)
+                    || event.hasModifiers(KeyEvent.KEYCODE_FUNCTION);
+        }
+
+        private boolean needsVerification(KeyEvent event) {
+            //TODO(b/331730488): Handle a11y events as well.
+            return verifyKeyEvent()
+                    && (hasKeyModifiers(event)
+                            || mInputMethodSession.onShouldVerifyKeyEvent(event));
+        }
     }
 }
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index dadb5c38..a8fde4a 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -56,6 +56,7 @@
 import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED;
 import static android.view.inputmethod.Flags.FLAG_CONNECTIONLESS_HANDWRITING;
 import static android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API;
+import static android.view.inputmethod.Flags.FLAG_VERIFY_KEY_EVENT;
 import static android.view.inputmethod.Flags.ctrlShiftShortcut;
 import static android.view.inputmethod.Flags.predictiveBackIme;
 
@@ -3735,6 +3736,23 @@
     }
 
     /**
+     * Received by the IME before dispatch to {@link #onKeyDown(int, KeyEvent)} to let the system
+     * know if the {@link KeyEvent} needs to be verified that it originated from the system.
+     * {@link KeyEvent}s may originate from outside of the system and any sensitive keys should be
+     * marked for verification. One example of this could be using key shortcuts for switching to
+     * another IME.
+     *
+     * @param keyEvent the event that may need verification.
+     * @return {@code true} if {@link KeyEvent} should have its HMAC verified before dispatch,
+     * {@code false} otherwise.
+     */
+    @FlaggedApi(FLAG_VERIFY_KEY_EVENT)
+    @Override
+    public boolean onShouldVerifyKeyEvent(@NonNull KeyEvent keyEvent) {
+        return false;
+    }
+
+    /**
      * Default implementation of {@link KeyEvent.Callback#onKeyLongPress(int, KeyEvent)
      * KeyEvent.Callback.onKeyLongPress()}: always returns false (doesn't handle
      * the event).
diff --git a/core/java/android/os/CpuHeadroomParams.java b/core/java/android/os/CpuHeadroomParams.java
new file mode 100644
index 0000000..f0d4f7d
--- /dev/null
+++ b/core/java/android/os/CpuHeadroomParams.java
@@ -0,0 +1,91 @@
+/*
+ * 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.os;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.os.health.SystemHealthManager;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Headroom request params used by {@link SystemHealthManager#getCpuHeadroom(CpuHeadroomParams)}.
+ */
+@FlaggedApi(Flags.FLAG_CPU_GPU_HEADROOMS)
+public final class CpuHeadroomParams {
+    final CpuHeadroomParamsInternal mInternal;
+
+    public CpuHeadroomParams() {
+        mInternal = new CpuHeadroomParamsInternal();
+    }
+
+    /** @hide */
+    @IntDef(flag = false, prefix = {"CPU_HEADROOM_CALCULATION_TYPE_"}, value = {
+            CPU_HEADROOM_CALCULATION_TYPE_MIN, // 0
+            CPU_HEADROOM_CALCULATION_TYPE_AVERAGE, // 1
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface CpuHeadroomCalculationType {
+    }
+
+    /**
+     * Calculates the headroom based on minimum value over a device-defined window.
+     */
+    public static final int CPU_HEADROOM_CALCULATION_TYPE_MIN = 0;
+
+    /**
+     * Calculates the headroom based on average value over a device-defined window.
+     */
+    public static final int CPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1;
+
+    /**
+     * Sets the headroom calculation type.
+     * <p>
+     *
+     * @throws IllegalArgumentException if the type is invalid.
+     */
+    public void setCalculationType(@CpuHeadroomCalculationType int calculationType) {
+        switch (calculationType) {
+            case CPU_HEADROOM_CALCULATION_TYPE_MIN:
+            case CPU_HEADROOM_CALCULATION_TYPE_AVERAGE:
+                mInternal.calculationType = (byte) calculationType;
+                return;
+        }
+        throw new IllegalArgumentException("Invalid calculation type: " + calculationType);
+    }
+
+    /**
+     * Gets the headroom calculation type.
+     * Default to {@link #CPU_HEADROOM_CALCULATION_TYPE_MIN} if not set.
+     */
+    public @CpuHeadroomCalculationType int getCalculationType() {
+        @CpuHeadroomCalculationType int validatedType = switch ((int) mInternal.calculationType) {
+            case CPU_HEADROOM_CALCULATION_TYPE_MIN, CPU_HEADROOM_CALCULATION_TYPE_AVERAGE ->
+                    mInternal.calculationType;
+            default -> CPU_HEADROOM_CALCULATION_TYPE_MIN;
+        };
+        return validatedType;
+    }
+
+    /**
+     * @hide
+     */
+    public CpuHeadroomParamsInternal getInternal() {
+        return mInternal;
+    }
+}
diff --git a/core/java/android/os/CpuHeadroomParamsInternal.aidl b/core/java/android/os/CpuHeadroomParamsInternal.aidl
new file mode 100644
index 0000000..6cc4699
--- /dev/null
+++ b/core/java/android/os/CpuHeadroomParamsInternal.aidl
@@ -0,0 +1,31 @@
+/*
+ * 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.os;
+
+import android.hardware.power.CpuHeadroomParams;
+
+/**
+ * Changes should be synced with match function of HintManagerService#CpuHeadroomCacheItem.
+ * {@hide}
+ */
+@JavaDerive(equals = true, toString = true)
+parcelable CpuHeadroomParamsInternal {
+    boolean usesDeviceHeadroom = false;
+    CpuHeadroomParams.CalculationType calculationType = CpuHeadroomParams.CalculationType.MIN;
+    CpuHeadroomParams.SelectionType selectionType = CpuHeadroomParams.SelectionType.ALL;
+}
+
diff --git a/core/java/android/os/GpuHeadroomParams.java b/core/java/android/os/GpuHeadroomParams.java
new file mode 100644
index 0000000..efb2a28
--- /dev/null
+++ b/core/java/android/os/GpuHeadroomParams.java
@@ -0,0 +1,91 @@
+/*
+ * 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.os;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.os.health.SystemHealthManager;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Headroom request params used by {@link SystemHealthManager#getGpuHeadroom(GpuHeadroomParams)}.
+ */
+@FlaggedApi(Flags.FLAG_CPU_GPU_HEADROOMS)
+public final class GpuHeadroomParams {
+    final GpuHeadroomParamsInternal mInternal;
+
+    public GpuHeadroomParams() {
+        mInternal = new GpuHeadroomParamsInternal();
+    }
+
+    /** @hide */
+    @IntDef(flag = false, prefix = {"GPU_HEADROOM_CALCULATION_TYPE_"}, value = {
+            GPU_HEADROOM_CALCULATION_TYPE_MIN, // 0
+            GPU_HEADROOM_CALCULATION_TYPE_AVERAGE, // 1
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface GpuHeadroomCalculationType {
+    }
+
+    /**
+     * Calculates the headroom based on minimum value over a device-defined window.
+     */
+    public static final int GPU_HEADROOM_CALCULATION_TYPE_MIN = 0;
+
+    /**
+     * Calculates the headroom based on average value over a device-defined window.
+     */
+    public static final int GPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1;
+
+    /**
+     * Sets the headroom calculation type.
+     * <p>
+     *
+     * @throws IllegalArgumentException if the type is invalid.
+     */
+    public void setCalculationType(@GpuHeadroomCalculationType int calculationType) {
+        switch (calculationType) {
+            case GPU_HEADROOM_CALCULATION_TYPE_MIN:
+            case GPU_HEADROOM_CALCULATION_TYPE_AVERAGE:
+                mInternal.calculationType = (byte) calculationType;
+                return;
+        }
+        throw new IllegalArgumentException("Invalid calculation type: " + calculationType);
+    }
+
+    /**
+     * Gets the headroom calculation type.
+     * Default to {@link #GPU_HEADROOM_CALCULATION_TYPE_MIN} if not set.
+     */
+    public @GpuHeadroomCalculationType int getCalculationType() {
+        @GpuHeadroomCalculationType int validatedType = switch ((int) mInternal.calculationType) {
+            case GPU_HEADROOM_CALCULATION_TYPE_MIN, GPU_HEADROOM_CALCULATION_TYPE_AVERAGE ->
+                    mInternal.calculationType;
+            default -> GPU_HEADROOM_CALCULATION_TYPE_MIN;
+        };
+        return validatedType;
+    }
+
+    /**
+     * @hide
+     */
+    public GpuHeadroomParamsInternal getInternal() {
+        return mInternal;
+    }
+}
diff --git a/core/java/android/os/GpuHeadroomParamsInternal.aidl b/core/java/android/os/GpuHeadroomParamsInternal.aidl
new file mode 100644
index 0000000..20309e7
--- /dev/null
+++ b/core/java/android/os/GpuHeadroomParamsInternal.aidl
@@ -0,0 +1,28 @@
+/*
+ * 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.os;
+
+import android.hardware.power.GpuHeadroomParams;
+
+/**
+ * Changes should be synced with match function of HintManagerService#GpuHeadroomCacheItem.
+ * {@hide}
+ */
+@JavaDerive(equals = true, toString = true)
+parcelable GpuHeadroomParamsInternal {
+    GpuHeadroomParams.CalculationType calculationType = GpuHeadroomParams.CalculationType.MIN;
+}
diff --git a/core/java/android/os/IHintManager.aidl b/core/java/android/os/IHintManager.aidl
index 73cdd56..3312055 100644
--- a/core/java/android/os/IHintManager.aidl
+++ b/core/java/android/os/IHintManager.aidl
@@ -17,6 +17,8 @@
 
 package android.os;
 
+import android.os.CpuHeadroomParamsInternal;
+import android.os.GpuHeadroomParamsInternal;
 import android.os.IHintSession;
 import android.hardware.power.ChannelConfig;
 import android.hardware.power.SessionConfig;
@@ -50,4 +52,8 @@
      */
     @nullable ChannelConfig getSessionChannel(in IBinder token);
     oneway void closeSessionChannel();
+    float[] getCpuHeadroom(in CpuHeadroomParamsInternal params);
+    long getCpuHeadroomMinIntervalMillis();
+    float getGpuHeadroom(in GpuHeadroomParamsInternal params);
+    long getGpuHeadroomMinIntervalMillis();
 }
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index 590ddb4..24e1d66 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -78,6 +78,9 @@
 # PermissionEnforcer
 per-file PermissionEnforcer.java = tweek@google.com, brufino@google.com
 
+# RemoteCallbackList
+per-file RemoteCallbackList.java = shayba@google.com
+
 # ART
 per-file ArtModuleServiceManager.java = file:platform/art:/OWNERS
 
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 9ab9228..5a53bc15 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -5308,7 +5308,13 @@
             Manifest.permission.MANAGE_USERS,
             Manifest.permission.CREATE_USERS,
             Manifest.permission.QUERY_USERS}, conditional = true)
+    @CachedProperty(api = "user_manager_user_data")
     public List<UserInfo> getProfiles(@UserIdInt int userId) {
+        if (android.multiuser.Flags.cacheProfilesReadOnly()) {
+            return UserManagerCache.getProfiles(
+                    (Integer userIdentifier) -> mService.getProfiles(userIdentifier, false),
+                    userId);
+        }
         try {
             return mService.getProfiles(userId, false /* enabledOnly */);
         } catch (RemoteException re) {
@@ -6484,6 +6490,19 @@
     }
 
     /**
+     * This method is used to invalidate caches, when UserManagerService.mUsers
+     * {@link UserManagerService.UserData} is modified, including changes to {@link UserInfo}.
+     * In practice we determine modification by when that data is persisted, or scheduled to be
+     * presisted, to xml.
+     * @hide
+     */
+    public static final void invalidateCacheOnUserDataChanged() {
+        if (android.multiuser.Flags.cacheProfilesReadOnly()) {
+            UserManagerCache.invalidateProfiles();
+        }
+    }
+
+    /**
      * Returns a serial number on this device for a given userId. User handles can be recycled
      * when deleting and creating users, but serial numbers are not reused until the device is
      * wiped.
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 9d15104..70cbc73 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -41,6 +41,7 @@
 import android.os.vibrator.RampSegment;
 import android.os.vibrator.StepSegment;
 import android.os.vibrator.VibrationEffectSegment;
+import android.os.vibrator.VibratorEnvelopeEffectInfo;
 import android.os.vibrator.VibratorFrequencyProfileLegacy;
 import android.util.MathUtils;
 
@@ -1908,12 +1909,12 @@
      *
      * <p>You can use the following APIs to obtain these limits:
      * <ul>
-     * <li>Maximum envelope control points: {@link Vibrator#getMaxEnvelopeEffectSize()}</li>
+     * <li>Maximum envelope control points: {@link VibratorEnvelopeEffectInfo#getMaxSize()}
      * <li>Minimum control point duration:
-     * {@link Vibrator#getMinEnvelopeEffectControlPointDurationMillis()}</li>
+     * {@link VibratorEnvelopeEffectInfo#getMinControlPointDurationMillis()}
      * <li>Maximum control point duration:
-     * {@link Vibrator#getMaxEnvelopeEffectControlPointDurationMillis()}</li>
-     * <li>Maximum total effect duration: {@link Vibrator#getMaxEnvelopeEffectDurationMillis()}</li>
+     * {@link VibratorEnvelopeEffectInfo#getMaxControlPointDurationMillis()}
+     * <li>Maximum total effect duration: {@link VibratorEnvelopeEffectInfo#getMaxDurationMillis()}
      * </ul>
      *
      * @see VibrationEffect#startWaveformEnvelope()
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 53f8a92..8620914 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -35,6 +35,7 @@
 import android.os.vibrator.Flags;
 import android.os.vibrator.VendorVibrationSession;
 import android.os.vibrator.VibrationConfig;
+import android.os.vibrator.VibratorEnvelopeEffectInfo;
 import android.os.vibrator.VibratorFrequencyProfile;
 import android.os.vibrator.VibratorFrequencyProfileLegacy;
 import android.util.Log;
@@ -137,6 +138,9 @@
     @Nullable
     private volatile VibrationConfig mVibrationConfig;
 
+    private VibratorFrequencyProfile mVibratorFrequencyProfile;
+    private VibratorEnvelopeEffectInfo mVibratorEnvelopeEffectInfo;
+
     /**
      * @hide to prevent subclassing from outside of the framework
      */
@@ -351,7 +355,11 @@
             return null;
         }
 
-        return new VibratorFrequencyProfile(frequencyProfile);
+        if (mVibratorFrequencyProfile == null) {
+            mVibratorFrequencyProfile = new VibratorFrequencyProfile(frequencyProfile);
+        }
+
+        return mVibratorFrequencyProfile;
     }
 
     /**
@@ -383,70 +391,28 @@
     }
 
     /**
-     * Retrieves the maximum duration supported for an envelope effect, in milliseconds.
+     * Retrieves the vibrator's capabilities and limitations for envelope effects.
      *
-     * <p>If the device supports envelope effects (check {@link #areEnvelopeEffectsSupported}),
-     * this value will be positive. Devices with envelope effects capabilities guarantees a
-     * maximum duration equivalent to the product of {@link #getMaxEnvelopeEffectSize()} and
-     * {@link #getMaxEnvelopeEffectControlPointDurationMillis()}. If the device does not support
-     * envelope effects, this method will return 0.
+     * <p>These parameters can be used with {@link VibrationEffect.WaveformEnvelopeBuilder}
+     * to create custom envelope effects.
      *
-     * @return The maximum duration (in milliseconds) allowed for an envelope effect, or 0 if
-     * envelope effects are not supported.
+     * @return The vibrator's envelope effect information, or null if not supported. If this
+     * vibrator is a composite of multiple physical devices then this will return a profile
+     * supported in all devices, or null if the intersection is empty or not available.
+     *
+     * @see VibrationEffect.WaveformEnvelopeBuilder
      */
     @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
-    public int getMaxEnvelopeEffectDurationMillis() {
-        return getInfo().getMaxEnvelopeEffectDurationMillis();
-    }
+    @NonNull
+    public VibratorEnvelopeEffectInfo getEnvelopeEffectInfo() {
+        if (mVibratorEnvelopeEffectInfo == null) {
+            mVibratorEnvelopeEffectInfo = new VibratorEnvelopeEffectInfo(
+                    getInfo().getMaxEnvelopeEffectSize(),
+                    getInfo().getMinEnvelopeEffectControlPointDurationMillis(),
+                    getInfo().getMaxEnvelopeEffectControlPointDurationMillis());
+        }
 
-    /**
-     * Retrieves the maximum number of control points supported for an envelope effect.
-     *
-     * <p>If the device supports envelope effects (check {@link #areEnvelopeEffectsSupported}),
-     * this value will be positive. Devices with envelope effects capabilities guarantee support
-     * for a minimum of 16 control points. If the device does not support envelope effects,
-     * this method will return 0.
-     *
-     * @return the maximum number of control points allowed for an envelope effect, or 0 if
-     * envelope effects are not supported.
-     */
-    @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
-    public int getMaxEnvelopeEffectSize() {
-        return getInfo().getMaxEnvelopeEffectSize();
-    }
-
-    /**
-     * Retrieves the minimum duration supported between two control points within an envelope
-     * effect, in milliseconds.
-     *
-     * <p>If the device supports envelope effects (check {@link #areEnvelopeEffectsSupported}),
-     * this value will be positive. Devices with envelope effects capabilities guarantee
-     * support for durations down to at least 20 milliseconds. If the device does
-     * not support envelope effects, this method will return 0.
-     *
-     * @return the minimum allowed duration between two control points in an envelope effect,
-     * or 0 if envelope effects are not supported.
-     */
-    @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
-    public int getMinEnvelopeEffectControlPointDurationMillis() {
-        return getInfo().getMinEnvelopeEffectControlPointDurationMillis();
-    }
-
-    /**
-     * Retrieves the maximum duration supported between two control points within an envelope
-     * effect, in milliseconds.
-     *
-     * <p>If the device supports envelope effects (check {@link #areEnvelopeEffectsSupported}),
-     * this value will be positive. Devices with envelope effects capabilities guarantee support
-     * for durations up to at least 1 second. If the device does not support envelope effects,
-     * this method will return 0.
-     *
-     * @return the maximum allowed duration between two control points in an envelope effect,
-     * or 0 if envelope effects are not supported.
-     */
-    @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
-    public int getMaxEnvelopeEffectControlPointDurationMillis() {
-        return getInfo().getMaxEnvelopeEffectControlPointDurationMillis();
+        return mVibratorEnvelopeEffectInfo;
     }
 
     /**
diff --git a/core/java/android/os/VibratorInfo.java b/core/java/android/os/VibratorInfo.java
index 9dec867..84325a4 100644
--- a/core/java/android/os/VibratorInfo.java
+++ b/core/java/android/os/VibratorInfo.java
@@ -121,7 +121,7 @@
      * @param qFactor                  The vibrator quality factor.
      * @param frequencyProfileLegacy   The description of the vibrator supported frequencies and max
      *                                 amplitude mappings.
-     * @param frequencyProfile       The description of the vibrator supported frequencies and
+     * @param frequencyProfile         The description of the vibrator supported frequencies and
      *                                 output acceleration mappings.
      * @param maxEnvelopeEffectSize    The maximum number of control points supported for an
      *                                 envelope effect.
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index d9db28e..084b7f7 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -66,6 +66,14 @@
 }
 
 flag {
+    name: "adpf_use_load_hints"
+    namespace: "game"
+    description: "Guards use of the ADPF public load hints behind a readonly flag"
+    is_fixed_read_only: true
+    bug: "367803904"
+}
+
+flag {
     name: "allow_consentless_bugreport_delegated_consent"
     namespace: "crumpet"
     description: "Allow privileged apps to call bugreport generation without enforcing user consent and delegate it to the calling app instead"
@@ -148,6 +156,13 @@
 }
 
 flag {
+    name: "cpu_gpu_headrooms"
+    namespace: "game"
+    description: "Feature flag for adding CPU/GPU headroom API"
+    bug: "346604998"
+}
+
+flag {
     name: "disallow_cellular_null_ciphers_restriction"
     namespace: "cellular_security"
     description: "Guards a new UserManager user restriction that admins can use to require cellular encryption on their managed devices."
diff --git a/core/java/android/os/health/SystemHealthManager.java b/core/java/android/os/health/SystemHealthManager.java
index deabfed..4db9bc3 100644
--- a/core/java/android/os/health/SystemHealthManager.java
+++ b/core/java/android/os/health/SystemHealthManager.java
@@ -17,6 +17,7 @@
 package android.os.health;
 
 import android.annotation.FlaggedApi;
+import android.annotation.FloatRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemService;
@@ -25,6 +26,11 @@
 import android.os.BatteryStats;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.CpuHeadroomParams;
+import android.os.CpuHeadroomParamsInternal;
+import android.os.GpuHeadroomParams;
+import android.os.GpuHeadroomParamsInternal;
+import android.os.IHintManager;
 import android.os.IPowerStatsService;
 import android.os.OutcomeReceiver;
 import android.os.PowerMonitor;
@@ -68,6 +74,8 @@
     private final IBatteryStats mBatteryStats;
     @Nullable
     private final IPowerStatsService mPowerStats;
+    @Nullable
+    private final IHintManager mHintManager;
     private List<PowerMonitor> mPowerMonitorsInfo;
     private final Object mPowerMonitorsLock = new Object();
     private static final long TAKE_UID_SNAPSHOT_TIMEOUT_MILLIS = 10_000;
@@ -88,14 +96,111 @@
     public SystemHealthManager() {
         this(IBatteryStats.Stub.asInterface(ServiceManager.getService(BatteryStats.SERVICE_NAME)),
                 IPowerStatsService.Stub.asInterface(
-                        ServiceManager.getService(Context.POWER_STATS_SERVICE)));
+                        ServiceManager.getService(Context.POWER_STATS_SERVICE)),
+                IHintManager.Stub.asInterface(
+                        ServiceManager.getService(Context.PERFORMANCE_HINT_SERVICE)));
     }
 
     /** {@hide} */
     public SystemHealthManager(@NonNull IBatteryStats batteryStats,
-            @Nullable IPowerStatsService powerStats) {
+            @Nullable IPowerStatsService powerStats, @Nullable IHintManager hintManager) {
         mBatteryStats = batteryStats;
         mPowerStats = powerStats;
+        mHintManager = hintManager;
+    }
+
+    /**
+     * Provides an estimate of global available CPU headroom of the calling thread.
+     * <p>
+     *
+     * @param  params params to customize the CPU headroom calculation, null to use default params.
+     * @return a single value a {@code Float.NaN} if it's temporarily unavailable.
+     *         A valid value is ranged from [0, 100], where 0 indicates no more CPU resources can be
+     *         granted.
+     * @throws UnsupportedOperationException if the API is unsupported or the request params can't
+     *         be served.
+     */
+    @FlaggedApi(android.os.Flags.FLAG_CPU_GPU_HEADROOMS)
+    public @FloatRange(from = 0f, to = 100f) float getCpuHeadroom(
+            @Nullable CpuHeadroomParams params) {
+        if (mHintManager == null) {
+            throw new UnsupportedOperationException();
+        }
+        try {
+            return mHintManager.getCpuHeadroom(
+                    params != null ? params.getInternal() : new CpuHeadroomParamsInternal())[0];
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+
+
+    /**
+     * Provides an estimate of global available GPU headroom of the device.
+     * <p>
+     *
+     * @param  params params to customize the GPU headroom calculation, null to use default params.
+     * @return a single value headroom or a {@code Float.NaN} if it's temporarily unavailable.
+     *         A valid value is ranged from [0, 100], where 0 indicates no more GPU resources can be
+     *         granted.
+     * @throws UnsupportedOperationException if the API is unsupported or the request params can't
+     *         be served.
+     */
+    @FlaggedApi(android.os.Flags.FLAG_CPU_GPU_HEADROOMS)
+    public @FloatRange(from = 0f, to = 100f) float getGpuHeadroom(
+            @Nullable GpuHeadroomParams params) {
+        if (mHintManager == null) {
+            throw new UnsupportedOperationException();
+        }
+        try {
+            return mHintManager.getGpuHeadroom(
+                    params != null ? params.getInternal() : new GpuHeadroomParamsInternal());
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Minimum polling interval for calling {@link #getCpuHeadroom(CpuHeadroomParams)} in
+     * milliseconds.
+     * <p>
+     * The {@link #getCpuHeadroom(CpuHeadroomParams)} API may return cached result if called more
+     * frequent than the interval.
+     *
+     * @throws UnsupportedOperationException if the API is unsupported.
+     */
+    @FlaggedApi(android.os.Flags.FLAG_CPU_GPU_HEADROOMS)
+    public long getCpuHeadroomMinIntervalMillis() {
+        if (mHintManager == null) {
+            throw new UnsupportedOperationException();
+        }
+        try {
+            return mHintManager.getCpuHeadroomMinIntervalMillis();
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Minimum polling interval for calling {@link #getGpuHeadroom(GpuHeadroomParams)} in
+     * milliseconds.
+     * <p>
+     * The {@link #getGpuHeadroom(GpuHeadroomParams)} API may return cached result if called more
+     * frequent than the interval.
+     *
+     * @throws UnsupportedOperationException if the API is unsupported.
+     */
+    @FlaggedApi(android.os.Flags.FLAG_CPU_GPU_HEADROOMS)
+    public long getGpuHeadroomMinIntervalMillis() {
+        if (mHintManager == null) {
+            throw new UnsupportedOperationException();
+        }
+        try {
+            return mHintManager.getGpuHeadroomMinIntervalMillis();
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -261,7 +366,7 @@
                         mPowerMonitorsInfo = result;
                     }
                     if (executor != null) {
-                        executor.execute(()-> onResult.accept(result));
+                        executor.execute(() -> onResult.accept(result));
                     } else {
                         onResult.accept(result);
                     }
diff --git a/core/java/android/os/vibrator/VibratorEnvelopeEffectInfo.java b/core/java/android/os/vibrator/VibratorEnvelopeEffectInfo.java
new file mode 100644
index 0000000..f2ad7a4
--- /dev/null
+++ b/core/java/android/os/vibrator/VibratorEnvelopeEffectInfo.java
@@ -0,0 +1,197 @@
+/*
+ * 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.os.vibrator;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.VibrationEffect;
+
+import java.util.Objects;
+
+/**
+ * Provides information about the vibrator hardware capabilities and limitations regarding
+ * waveform envelope effects. This includes:
+ * <ul>
+ * <li>Maximum number of control points supported.
+ * <li>Minimum and maximum duration for individual segments.
+ * <li>Maximum total duration for an envelope effect.
+ * </ul>
+ *
+ * <p>This information can be used to help construct waveform envelope effects with
+ * {@link VibrationEffect#startWaveformEnvelope()}. When designing these effects, it is also
+ * recommended to check the {@link VibratorFrequencyProfile} for information about the supported
+ * frequency range and the vibrator's output response.
+ *
+ * @see VibrationEffect#startWaveformEnvelope()
+ * @see VibratorFrequencyProfile
+ */
+@FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+public final class VibratorEnvelopeEffectInfo implements Parcelable {
+    private final int mMaxSize;
+    private final long mMinControlPointDurationMillis;
+    private final long mMaxControlPointDurationMillis;
+
+    VibratorEnvelopeEffectInfo(Parcel in) {
+        mMaxSize = in.readInt();
+        mMinControlPointDurationMillis = in.readLong();
+        mMaxControlPointDurationMillis = in.readLong();
+    }
+
+    /**
+     * Default constructor.
+     *
+     * @param maxSize                       The maximum number of control points supported for an
+     *                                      envelope effect.
+     * @param minControlPointDurationMillis The minimum duration supported between two control
+     *                                      points within an envelope effect.
+     * @param maxControlPointDurationMillis The maximum duration supported between two control
+     *                                      points within an envelope effect.
+     * @hide
+     */
+    public VibratorEnvelopeEffectInfo(int maxSize,
+            long minControlPointDurationMillis,
+            long maxControlPointDurationMillis) {
+        mMaxSize = maxSize;
+        mMinControlPointDurationMillis = minControlPointDurationMillis;
+        mMaxControlPointDurationMillis = maxControlPointDurationMillis;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mMaxSize);
+        dest.writeLong(mMinControlPointDurationMillis);
+        dest.writeLong(mMaxControlPointDurationMillis);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof VibratorEnvelopeEffectInfo)) {
+            return false;
+        }
+        VibratorEnvelopeEffectInfo other = (VibratorEnvelopeEffectInfo) o;
+        return mMaxSize == other.mMaxSize
+                && mMinControlPointDurationMillis == other.mMinControlPointDurationMillis
+                && mMaxControlPointDurationMillis == other.mMaxControlPointDurationMillis;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mMaxSize,
+                mMinControlPointDurationMillis,
+                mMaxControlPointDurationMillis);
+    }
+
+    @Override
+    public String toString() {
+        return "VibratorEnvelopeEffectInfo{"
+                + ", mMaxSize=" + mMaxSize
+                + ", mMinControlPointDurationMillis=" + mMinControlPointDurationMillis
+                + ", mMaxControlPointDurationMillis=" + mMaxControlPointDurationMillis
+                + '}';
+    }
+
+    @NonNull
+    public static final Creator<VibratorEnvelopeEffectInfo> CREATOR =
+            new Creator<VibratorEnvelopeEffectInfo>() {
+                @Override
+                public VibratorEnvelopeEffectInfo createFromParcel(Parcel in) {
+                    return new VibratorEnvelopeEffectInfo(in);
+                }
+
+                @Override
+                public VibratorEnvelopeEffectInfo[] newArray(int size) {
+                    return new VibratorEnvelopeEffectInfo[size];
+                }
+            };
+
+    /**
+     * Retrieves the maximum duration supported for an envelope effect, in milliseconds.
+     *
+     * <p>If the device supports envelope effects
+     * (check {@link android.os.VibratorInfo#areEnvelopeEffectsSupported}), this value will be
+     * positive. Devices with envelope effects capabilities guarantees a maximum duration
+     * equivalent to the product of {@link #getMaxSize()} and
+     * {@link #getMaxControlPointDurationMillis()}. If the device does not support
+     * envelope effects, this method will return 0.
+     *
+     * @return The maximum duration (in milliseconds) allowed for an envelope effect, or 0 if
+     * envelope effects are not supported.
+     */
+    public long getMaxDurationMillis() {
+        return mMaxSize * mMaxControlPointDurationMillis;
+    }
+
+    /**
+     * Retrieves the maximum number of control points supported for an envelope effect.
+     *
+     * <p>If the device supports envelope effects
+     * (check {@link android.os.VibratorInfo#areEnvelopeEffectsSupported}), this value will be
+     * positive. Devices with envelope effects capabilities guarantee support for a minimum of
+     * 16 control points. If the device does not support envelope effects, this method will
+     * return 0.
+     *
+     * @return the maximum number of control points allowed for an envelope effect, or 0 if
+     * envelope effects are not supported.
+     */
+    public int getMaxSize() {
+        return mMaxSize;
+    }
+
+    /**
+     * Retrieves the minimum duration supported between two control points within an envelope
+     * effect, in milliseconds.
+     *
+     * <p>If the device supports envelope effects
+     * (check {@link android.os.VibratorInfo#areEnvelopeEffectsSupported}), this value will be
+     * positive. Devices with envelope effects capabilities guarantee support for durations down
+     * to at least 20 milliseconds. If the device does not support envelope effects,
+     * this method will return 0.
+     *
+     * @return the minimum allowed duration between two control points in an envelope effect,
+     * or 0 if envelope effects are not supported.
+     */
+    public long getMinControlPointDurationMillis() {
+        return mMinControlPointDurationMillis;
+    }
+
+    /**
+     * Retrieves the maximum duration supported between two control points within an envelope
+     * effect, in milliseconds.
+     *
+     * <p>If the device supports envelope effects
+     * (check {@link android.os.VibratorInfo#areEnvelopeEffectsSupported}), this value will be
+     * positive. Devices with envelope effects capabilities guarantee support for durations up to
+     * at least 1 second. If the device does not support envelope effects, this method
+     * will return 0.
+     *
+     * @return the maximum allowed duration between two control points in an envelope effect,
+     * or 0 if envelope effects are not supported.
+     */
+    public long getMaxControlPointDurationMillis() {
+        return mMaxControlPointDurationMillis;
+    }
+}
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index ce90121..09004b3 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -115,6 +115,14 @@
 }
 
 flag {
+    name: "protect_device_config_flags"
+    namespace: "psap_ai"
+    description: "Feature flag to limit adb shell to allowlisted flags"
+    bug: "364083026"
+    is_fixed_read_only: true
+}
+
+flag {
     name: "keystore_grant_api"
     namespace: "hardware_backed_security"
     description: "Feature flag for exposing KeyStore grant APIs"
diff --git a/core/java/android/security/responsible_apis_flags.aconfig b/core/java/android/security/responsible_apis_flags.aconfig
index 66e1f38..6c92991 100644
--- a/core/java/android/security/responsible_apis_flags.aconfig
+++ b/core/java/android/security/responsible_apis_flags.aconfig
@@ -103,3 +103,10 @@
     description: "Applies intentMatchingFlags while matching intents to application components"
     bug: "364354494"
 }
+
+flag {
+    name: "aapm_feature_disable_install_unknown_sources"
+    namespace: "responsible_apis"
+    description: "Android Advanced Protection Mode Feature: Disable Install Unknown Sources"
+    bug: "369361373"
+}
diff --git a/core/java/android/service/quickaccesswallet/flags.aconfig b/core/java/android/service/quickaccesswallet/flags.aconfig
index 07311d5..75a9309 100644
--- a/core/java/android/service/quickaccesswallet/flags.aconfig
+++ b/core/java/android/service/quickaccesswallet/flags.aconfig
@@ -3,7 +3,7 @@
 
 flag {
     name: "launch_wallet_option_on_power_double_tap"
-    namespace: "wallet_integrations"
+    namespace: "wallet_integration"
     description: "Option to launch the Wallet app on double-tap of the power button"
     bug: "378469025"
 }
\ No newline at end of file
diff --git a/core/java/android/telephony/SubscriptionPlan.java b/core/java/android/telephony/SubscriptionPlan.java
index 7b48a16..4c59a85 100644
--- a/core/java/android/telephony/SubscriptionPlan.java
+++ b/core/java/android/telephony/SubscriptionPlan.java
@@ -18,6 +18,7 @@
 
 import android.annotation.BytesLong;
 import android.annotation.CurrentTimeMillisLong;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -28,6 +29,7 @@
 import android.util.Range;
 import android.util.RecurrenceRule;
 
+import com.android.internal.telephony.flags.Flags;
 import com.android.internal.util.Preconditions;
 
 import java.lang.annotation.Retention;
@@ -83,6 +85,33 @@
     /** Value indicating a timestamp is unknown. */
     public static final long TIME_UNKNOWN = -1;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "SUBSCRIPTION_STATUS_" }, value = {
+            SUBSCRIPTION_STATUS_UNKNOWN,
+            SUBSCRIPTION_STATUS_ACTIVE,
+            SUBSCRIPTION_STATUS_INACTIVE,
+            SUBSCRIPTION_STATUS_TRIAL,
+            SUBSCRIPTION_STATUS_SUSPENDED
+    })
+    public @interface SubscriptionStatus {}
+
+    /** Subscription status is unknown. */
+    @FlaggedApi(Flags.FLAG_SUBSCRIPTION_PLAN_ALLOW_STATUS_AND_END_DATE)
+    public static final int SUBSCRIPTION_STATUS_UNKNOWN = 0;
+    /** Subscription is active. */
+    @FlaggedApi(Flags.FLAG_SUBSCRIPTION_PLAN_ALLOW_STATUS_AND_END_DATE)
+    public static final int SUBSCRIPTION_STATUS_ACTIVE = 1;
+    /** Subscription is inactive. */
+    @FlaggedApi(Flags.FLAG_SUBSCRIPTION_PLAN_ALLOW_STATUS_AND_END_DATE)
+    public static final int SUBSCRIPTION_STATUS_INACTIVE = 2;
+    /** Subscription is in a trial period. */
+    @FlaggedApi(Flags.FLAG_SUBSCRIPTION_PLAN_ALLOW_STATUS_AND_END_DATE)
+    public static final int SUBSCRIPTION_STATUS_TRIAL = 3;
+    /** Subscription is suspended. */
+    @FlaggedApi(Flags.FLAG_SUBSCRIPTION_PLAN_ALLOW_STATUS_AND_END_DATE)
+    public static final int SUBSCRIPTION_STATUS_SUSPENDED = 4;
+
     private final RecurrenceRule cycleRule;
     private CharSequence title;
     private CharSequence summary;
@@ -91,6 +120,7 @@
     private long dataUsageBytes = BYTES_UNKNOWN;
     private long dataUsageTime = TIME_UNKNOWN;
     private @NetworkType int[] networkTypes;
+    private int mSubscriptionStatus = SUBSCRIPTION_STATUS_UNKNOWN;
 
     private SubscriptionPlan(RecurrenceRule cycleRule) {
         this.cycleRule = Preconditions.checkNotNull(cycleRule);
@@ -107,6 +137,7 @@
         dataUsageBytes = source.readLong();
         dataUsageTime = source.readLong();
         networkTypes = source.createIntArray();
+        mSubscriptionStatus = source.readInt();
     }
 
     @Override
@@ -124,6 +155,7 @@
         dest.writeLong(dataUsageBytes);
         dest.writeLong(dataUsageTime);
         dest.writeIntArray(networkTypes);
+        dest.writeInt(mSubscriptionStatus);
     }
 
     @Override
@@ -137,13 +169,14 @@
                 .append(" dataUsageBytes=").append(dataUsageBytes)
                 .append(" dataUsageTime=").append(dataUsageTime)
                 .append(" networkTypes=").append(Arrays.toString(networkTypes))
+                .append(" subscriptionStatus=").append(mSubscriptionStatus)
                 .append("}").toString();
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(cycleRule, title, summary, dataLimitBytes, dataLimitBehavior,
-                dataUsageBytes, dataUsageTime, Arrays.hashCode(networkTypes));
+                dataUsageBytes, dataUsageTime, Arrays.hashCode(networkTypes), mSubscriptionStatus);
     }
 
     @Override
@@ -157,7 +190,8 @@
                     && dataLimitBehavior == other.dataLimitBehavior
                     && dataUsageBytes == other.dataUsageBytes
                     && dataUsageTime == other.dataUsageTime
-                    && Arrays.equals(networkTypes, other.networkTypes);
+                    && Arrays.equals(networkTypes, other.networkTypes)
+                    && mSubscriptionStatus == other.mSubscriptionStatus;
         }
         return false;
     }
@@ -179,6 +213,13 @@
         return cycleRule;
     }
 
+    /** Return the end date of this plan, or null if no end date exists. */
+    @FlaggedApi(Flags.FLAG_SUBSCRIPTION_PLAN_ALLOW_STATUS_AND_END_DATE)
+    public @Nullable ZonedDateTime getPlanEndDate() {
+        // ZonedDateTime is immutable, so no need to create a defensive copy.
+        return cycleRule.end;
+    }
+
     /** Return the short title of this plan. */
     public @Nullable CharSequence getTitle() {
         return title;
@@ -238,6 +279,16 @@
     }
 
     /**
+     * Returns the status of the subscription plan.
+     *
+     * @return The subscription status, or {@link #SUBSCRIPTION_STATUS_UNKNOWN} if not available.
+     */
+    @FlaggedApi(Flags.FLAG_SUBSCRIPTION_PLAN_ALLOW_STATUS_AND_END_DATE)
+    public @SubscriptionStatus int getSubscriptionStatus() {
+        return mSubscriptionStatus;
+    }
+
+    /**
      * Builder for a {@link SubscriptionPlan}.
      */
     public static class Builder {
@@ -382,5 +433,21 @@
                     TelephonyManager.getAllNetworkTypes().length);
             return this;
         }
+
+        /**
+         * Set the subscription status.
+         *
+         * @param subscriptionStatus the current subscription status
+         */
+        @FlaggedApi(Flags.FLAG_SUBSCRIPTION_PLAN_ALLOW_STATUS_AND_END_DATE)
+        public @NonNull Builder setSubscriptionStatus(@SubscriptionStatus int subscriptionStatus) {
+            if (subscriptionStatus < SUBSCRIPTION_STATUS_UNKNOWN
+                    || subscriptionStatus > SUBSCRIPTION_STATUS_SUSPENDED) {
+                throw new IllegalArgumentException(
+                        "Subscription status must be defined with a valid value");
+            }
+            plan.mSubscriptionStatus = subscriptionStatus;
+            return this;
+        }
     }
 }
diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java
deleted file mode 100644
index f69a333..0000000
--- a/core/java/android/text/TextFlags.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.text;
-
-import android.annotation.NonNull;
-import android.app.AppGlobals;
-
-/**
- * Flags in the "text" namespace.
- *
- * TODO(nona): Remove this class.
- * @hide
- */
-public final class TextFlags {
-
-    /**
-     * The name space of the "text" feature.
-     *
-     * This needs to move to DeviceConfig constant.
-     */
-    public static final String NAMESPACE = "text";
-
-    /**
-     * Whether we use the new design of context menu.
-     */
-    public static final String ENABLE_NEW_CONTEXT_MENU =
-            "TextEditing__enable_new_context_menu";
-
-    /**
-     * The key name used in app core settings for {@link #ENABLE_NEW_CONTEXT_MENU}.
-     */
-    public static final String KEY_ENABLE_NEW_CONTEXT_MENU = "text__enable_new_context_menu";
-
-    /**
-     * Default value for the flag {@link #ENABLE_NEW_CONTEXT_MENU}.
-     */
-    public static final boolean ENABLE_NEW_CONTEXT_MENU_DEFAULT = true;
-
-    /**
-     * List of text flags to be transferred to the application process.
-     */
-    public static final String[] TEXT_ACONFIGS_FLAGS = {
-    };
-
-    /**
-     * List of the default values of the text flags.
-     *
-     * The order must be the same to the TEXT_ACONFIG_FLAGS.
-     */
-    public static final boolean[] TEXT_ACONFIG_DEFAULT_VALUE = {
-    };
-
-    /**
-     * Get a key for the feature flag.
-     */
-    public static String getKeyForFlag(@NonNull String flag) {
-        return "text__" + flag;
-    }
-
-    /**
-     * Return true if the feature flag is enabled.
-     */
-    public static boolean isFeatureEnabled(@NonNull String flag) {
-        return AppGlobals.getIntCoreSetting(
-                getKeyForFlag(flag), 0 /* aconfig is false by default */) != 0;
-    }
-}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 68efa79..d56768d 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -4566,14 +4566,31 @@
             return this;
         }
 
-        /** @hide */
+        /**
+         * Sets the Luts for the layer.
+         *
+         * <p> The function also allows to clear previously applied lut(s). To do this,
+         * set the displayluts to be either {@code nullptr} or
+         * an empty {@link android.hardware.DisplayLuts} instance.
+         *
+         * @param sc The SurfaceControl to update
+         *
+         * @param displayLuts The selected Lut(s)
+         *
+         * @return this
+         * @see DisplayLuts
+         */
+        @FlaggedApi(android.hardware.flags.Flags.FLAG_LUTS_API)
         public @NonNull Transaction setLuts(@NonNull SurfaceControl sc,
-                @NonNull DisplayLuts displayLuts) {
+                @Nullable DisplayLuts displayLuts) {
             checkPreconditions(sc);
-
-            nativeSetLuts(mNativeObject, sc.mNativeObject, displayLuts.getLutBuffers(),
-                    displayLuts.getOffsets(), displayLuts.getLutDimensions(),
-                    displayLuts.getLutSizes(), displayLuts.getLutSamplingKeys());
+            if (displayLuts != null && displayLuts.valid()) {
+                nativeSetLuts(mNativeObject, sc.mNativeObject, displayLuts.getLutBuffers(),
+                        displayLuts.getOffsets(), displayLuts.getLutDimensions(),
+                        displayLuts.getLutSizes(), displayLuts.getLutSamplingKeys());
+            } else {
+                nativeSetLuts(mNativeObject, sc.mNativeObject, null, null, null, null, null);
+            }
             return this;
         }
 
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index a560339..afe195c 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -523,7 +523,6 @@
     @Nullable
     public LocaleList hintLocales = null;
 
-
     /**
      * List of acceptable MIME types for
      * {@link InputConnection#commitContent(InputContentInfo, int, Bundle)}.
@@ -758,6 +757,30 @@
         return mIsStylusHandwritingEnabled;
     }
 
+    private boolean mWritingToolsEnabled = true;
+
+    /**
+     * Returns {@code true} when an {@code Editor} has writing tools enabled.
+     * {@code true} by default for all editors. Toolkits can optionally disable them where not
+     * relevant e.g. passwords, number input, etc.
+     * @see #setWritingToolsEnabled(boolean)
+     */
+    @FlaggedApi(Flags.FLAG_WRITING_TOOLS)
+    public boolean isWritingToolsEnabled() {
+        return mWritingToolsEnabled;
+    }
+
+    /**
+     * Set {@code false} if {@code Editor} opts-out of writing tools, that enable IMEs to replace
+     * text with generative AI text.
+     * @param enabled set {@code true} to enabled or {@code false to disable} support.
+     * @see #isWritingToolsEnabled()
+     */
+    @FlaggedApi(Flags.FLAG_WRITING_TOOLS)
+    public void setWritingToolsEnabled(boolean enabled) {
+        mWritingToolsEnabled = enabled;
+    }
+
     /**
      * If not {@code null}, this editor needs to talk to IMEs that run for the specified user, no
      * matter what user ID the calling process has.
@@ -1276,6 +1299,7 @@
                 + InputMethodDebug.handwritingGestureTypeFlagsToString(
                         mSupportedHandwritingGesturePreviewTypes));
         pw.println(prefix + "isStylusHandwritingEnabled=" + mIsStylusHandwritingEnabled);
+        pw.println(prefix + "writingToolsEnabled=" + mWritingToolsEnabled);
         pw.println(prefix + "contentMimeTypes=" + Arrays.toString(contentMimeTypes));
         if (targetInputMethodUser != null) {
             pw.println(prefix + "targetInputMethodUserId=" + targetInputMethodUser.getIdentifier());
@@ -1356,6 +1380,7 @@
         }
         dest.writeStringArray(contentMimeTypes);
         UserHandle.writeToParcel(targetInputMethodUser, dest);
+        dest.writeBoolean(mWritingToolsEnabled);
     }
 
     /**
@@ -1396,6 +1421,7 @@
                     res.hintLocales = hintLocales.isEmpty() ? null : hintLocales;
                     res.contentMimeTypes = source.readStringArray();
                     res.targetInputMethodUser = UserHandle.readFromParcel(source);
+                    res.mWritingToolsEnabled = source.readBoolean();
                     return res;
                 }
 
diff --git a/core/java/android/view/inputmethod/InputMethodSession.java b/core/java/android/view/inputmethod/InputMethodSession.java
index 4f48cb6..0f48f12 100644
--- a/core/java/android/view/inputmethod/InputMethodSession.java
+++ b/core/java/android/view/inputmethod/InputMethodSession.java
@@ -16,6 +16,7 @@
 
 package android.view.inputmethod;
 
+import android.annotation.NonNull;
 import android.graphics.Rect;
 import android.inputmethodservice.InputMethodService;
 import android.os.Bundle;
@@ -125,6 +126,11 @@
     public void dispatchKeyEvent(int seq, KeyEvent event, EventCallback callback);
 
     /**
+     * @hide
+     */
+    boolean onShouldVerifyKeyEvent(@NonNull KeyEvent event);
+
+    /**
      * This method is called when there is a track ball event.
      *
      * <p>
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index e619ab0..63f8c80 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -175,3 +175,11 @@
   bug: "342672560"
   is_fixed_read_only: true
 }
+
+flag {
+  name: "verify_key_event"
+  namespace: "input_method"
+  description: "Verify KeyEvents in IME"
+  bug: "331730488"
+  is_fixed_read_only: true
+}
\ No newline at end of file
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index d7750bd..cb70466 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -106,6 +106,7 @@
 import android.os.ParcelableParcel;
 import android.os.Process;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.BoringLayout;
@@ -9229,174 +9230,179 @@
 
     @Override
     protected void onDraw(Canvas canvas) {
-        restartMarqueeIfNeeded();
+        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "TextView.onDraw");
+        try {
+            restartMarqueeIfNeeded();
 
-        // Draw the background for this view
-        super.onDraw(canvas);
+            // Draw the background for this view
+            super.onDraw(canvas);
 
-        final int compoundPaddingLeft = getCompoundPaddingLeft();
-        final int compoundPaddingTop = getCompoundPaddingTop();
-        final int compoundPaddingRight = getCompoundPaddingRight();
-        final int compoundPaddingBottom = getCompoundPaddingBottom();
-        final int scrollX = mScrollX;
-        final int scrollY = mScrollY;
-        final int right = mRight;
-        final int left = mLeft;
-        final int bottom = mBottom;
-        final int top = mTop;
-        final boolean isLayoutRtl = isLayoutRtl();
-        final int offset = getHorizontalOffsetForDrawables();
-        final int leftOffset = isLayoutRtl ? 0 : offset;
-        final int rightOffset = isLayoutRtl ? offset : 0;
+            final int compoundPaddingLeft = getCompoundPaddingLeft();
+            final int compoundPaddingTop = getCompoundPaddingTop();
+            final int compoundPaddingRight = getCompoundPaddingRight();
+            final int compoundPaddingBottom = getCompoundPaddingBottom();
+            final int scrollX = mScrollX;
+            final int scrollY = mScrollY;
+            final int right = mRight;
+            final int left = mLeft;
+            final int bottom = mBottom;
+            final int top = mTop;
+            final boolean isLayoutRtl = isLayoutRtl();
+            final int offset = getHorizontalOffsetForDrawables();
+            final int leftOffset = isLayoutRtl ? 0 : offset;
+            final int rightOffset = isLayoutRtl ? offset : 0;
 
-        final Drawables dr = mDrawables;
-        if (dr != null) {
-            /*
-             * Compound, not extended, because the icon is not clipped
-             * if the text height is smaller.
-             */
+            final Drawables dr = mDrawables;
+            if (dr != null) {
+                /*
+                 * Compound, not extended, because the icon is not clipped
+                 * if the text height is smaller.
+                 */
 
-            int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
-            int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
+                int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
+                int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
 
-            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
-            // Make sure to update invalidateDrawable() when changing this code.
-            if (dr.mShowing[Drawables.LEFT] != null) {
-                canvas.save();
-                canvas.translate(scrollX + mPaddingLeft + leftOffset,
-                        scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2);
-                dr.mShowing[Drawables.LEFT].draw(canvas);
-                canvas.restore();
+                // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
+                // Make sure to update invalidateDrawable() when changing this code.
+                if (dr.mShowing[Drawables.LEFT] != null) {
+                    canvas.save();
+                    canvas.translate(scrollX + mPaddingLeft + leftOffset,
+                            scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2);
+                    dr.mShowing[Drawables.LEFT].draw(canvas);
+                    canvas.restore();
+                }
+
+                // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
+                // Make sure to update invalidateDrawable() when changing this code.
+                if (dr.mShowing[Drawables.RIGHT] != null) {
+                    canvas.save();
+                    canvas.translate(scrollX + right - left - mPaddingRight
+                                    - dr.mDrawableSizeRight - rightOffset,
+                            scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
+                    dr.mShowing[Drawables.RIGHT].draw(canvas);
+                    canvas.restore();
+                }
+
+                // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
+                // Make sure to update invalidateDrawable() when changing this code.
+                if (dr.mShowing[Drawables.TOP] != null) {
+                    canvas.save();
+                    canvas.translate(scrollX + compoundPaddingLeft
+                            + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
+                    dr.mShowing[Drawables.TOP].draw(canvas);
+                    canvas.restore();
+                }
+
+                // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
+                // Make sure to update invalidateDrawable() when changing this code.
+                if (dr.mShowing[Drawables.BOTTOM] != null) {
+                    canvas.save();
+                    canvas.translate(scrollX + compoundPaddingLeft
+                                    + (hspace - dr.mDrawableWidthBottom) / 2,
+                            scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
+                    dr.mShowing[Drawables.BOTTOM].draw(canvas);
+                    canvas.restore();
+                }
             }
 
-            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
-            // Make sure to update invalidateDrawable() when changing this code.
-            if (dr.mShowing[Drawables.RIGHT] != null) {
-                canvas.save();
-                canvas.translate(scrollX + right - left - mPaddingRight
-                        - dr.mDrawableSizeRight - rightOffset,
-                         scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
-                dr.mShowing[Drawables.RIGHT].draw(canvas);
-                canvas.restore();
+            int color = mCurTextColor;
+
+            if (mLayout == null) {
+                assumeLayout();
             }
 
-            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
-            // Make sure to update invalidateDrawable() when changing this code.
-            if (dr.mShowing[Drawables.TOP] != null) {
-                canvas.save();
-                canvas.translate(scrollX + compoundPaddingLeft
-                        + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
-                dr.mShowing[Drawables.TOP].draw(canvas);
-                canvas.restore();
+            Layout layout = mLayout;
+
+            if (mHint != null && !mHideHint && mText.length() == 0) {
+                if (mHintTextColor != null) {
+                    color = mCurHintTextColor;
+                }
+
+                layout = mHintLayout;
             }
 
-            // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
-            // Make sure to update invalidateDrawable() when changing this code.
-            if (dr.mShowing[Drawables.BOTTOM] != null) {
-                canvas.save();
-                canvas.translate(scrollX + compoundPaddingLeft
-                        + (hspace - dr.mDrawableWidthBottom) / 2,
-                         scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
-                dr.mShowing[Drawables.BOTTOM].draw(canvas);
-                canvas.restore();
-            }
-        }
+            mTextPaint.setColor(color);
+            mTextPaint.drawableState = getDrawableState();
 
-        int color = mCurTextColor;
+            canvas.save();
+            /*  Would be faster if we didn't have to do this. Can we chop the
+                (displayable) text so that we don't need to do this ever?
+            */
 
-        if (mLayout == null) {
-            assumeLayout();
-        }
+            int extendedPaddingTop = getExtendedPaddingTop();
+            int extendedPaddingBottom = getExtendedPaddingBottom();
 
-        Layout layout = mLayout;
+            final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
+            final int maxScrollY = mLayout.getHeight() - vspace;
 
-        if (mHint != null && !mHideHint && mText.length() == 0) {
-            if (mHintTextColor != null) {
-                color = mCurHintTextColor;
+            float clipLeft = compoundPaddingLeft + scrollX;
+            float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
+            float clipRight = right - left - getCompoundPaddingRight() + scrollX;
+            float clipBottom = bottom - top + scrollY
+                    - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
+
+            if (mShadowRadius != 0) {
+                clipLeft += Math.min(0, mShadowDx - mShadowRadius);
+                clipRight += Math.max(0, mShadowDx + mShadowRadius);
+
+                clipTop += Math.min(0, mShadowDy - mShadowRadius);
+                clipBottom += Math.max(0, mShadowDy + mShadowRadius);
             }
 
-            layout = mHintLayout;
-        }
+            canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
 
-        mTextPaint.setColor(color);
-        mTextPaint.drawableState = getDrawableState();
+            int voffsetText = 0;
+            int voffsetCursor = 0;
 
-        canvas.save();
-        /*  Would be faster if we didn't have to do this. Can we chop the
-            (displayable) text so that we don't need to do this ever?
-        */
+            // translate in by our padding
+            /* shortcircuit calling getVerticaOffset() */
+            if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
+                voffsetText = getVerticalOffset(false);
+                voffsetCursor = getVerticalOffset(true);
+            }
+            canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
 
-        int extendedPaddingTop = getExtendedPaddingTop();
-        int extendedPaddingBottom = getExtendedPaddingBottom();
+            final int layoutDirection = getLayoutDirection();
+            final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
+            if (isMarqueeFadeEnabled()) {
+                if (!mSingleLine && getLineCount() == 1 && canMarquee()
+                        && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
+                    final int width = mRight - mLeft;
+                    final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
+                    final float dx = mLayout.getLineRight(0) - (width - padding);
+                    canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
+                }
 
-        final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
-        final int maxScrollY = mLayout.getHeight() - vspace;
+                if (mMarquee != null && mMarquee.isRunning()) {
+                    final float dx = -mMarquee.getScroll();
+                    canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
+                }
+            }
 
-        float clipLeft = compoundPaddingLeft + scrollX;
-        float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
-        float clipRight = right - left - getCompoundPaddingRight() + scrollX;
-        float clipBottom = bottom - top + scrollY
-                - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
+            final int cursorOffsetVertical = voffsetCursor - voffsetText;
 
-        if (mShadowRadius != 0) {
-            clipLeft += Math.min(0, mShadowDx - mShadowRadius);
-            clipRight += Math.max(0, mShadowDx + mShadowRadius);
+            maybeUpdateHighlightPaths();
+            // If there is a gesture preview highlight, then the selection or cursor is not drawn.
+            Path highlight = hasGesturePreviewHighlight() ? null : getUpdatedHighlightPath();
+            if (mEditor != null) {
+                mEditor.onDraw(canvas, layout, mHighlightPaths, mHighlightPaints, highlight,
+                        mHighlightPaint, cursorOffsetVertical);
+            } else {
+                layout.draw(canvas, mHighlightPaths, mHighlightPaints, highlight, mHighlightPaint,
+                        cursorOffsetVertical);
+            }
 
-            clipTop += Math.min(0, mShadowDy - mShadowRadius);
-            clipBottom += Math.max(0, mShadowDy + mShadowRadius);
-        }
-
-        canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
-
-        int voffsetText = 0;
-        int voffsetCursor = 0;
-
-        // translate in by our padding
-        /* shortcircuit calling getVerticaOffset() */
-        if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
-            voffsetText = getVerticalOffset(false);
-            voffsetCursor = getVerticalOffset(true);
-        }
-        canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
-
-        final int layoutDirection = getLayoutDirection();
-        final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
-        if (isMarqueeFadeEnabled()) {
-            if (!mSingleLine && getLineCount() == 1 && canMarquee()
-                    && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
-                final int width = mRight - mLeft;
-                final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
-                final float dx = mLayout.getLineRight(0) - (width - padding);
+            if (mMarquee != null && mMarquee.shouldDrawGhost()) {
+                final float dx = mMarquee.getGhostOffset();
                 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
+                layout.draw(canvas, mHighlightPaths, mHighlightPaints, highlight, mHighlightPaint,
+                        cursorOffsetVertical);
             }
 
-            if (mMarquee != null && mMarquee.isRunning()) {
-                final float dx = -mMarquee.getScroll();
-                canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
-            }
+            canvas.restore();
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
         }
-
-        final int cursorOffsetVertical = voffsetCursor - voffsetText;
-
-        maybeUpdateHighlightPaths();
-        // If there is a gesture preview highlight, then the selection or cursor is not drawn.
-        Path highlight = hasGesturePreviewHighlight() ? null : getUpdatedHighlightPath();
-        if (mEditor != null) {
-            mEditor.onDraw(canvas, layout, mHighlightPaths, mHighlightPaints, highlight,
-                    mHighlightPaint, cursorOffsetVertical);
-        } else {
-            layout.draw(canvas, mHighlightPaths, mHighlightPaints, highlight, mHighlightPaint,
-                    cursorOffsetVertical);
-        }
-
-        if (mMarquee != null && mMarquee.shouldDrawGhost()) {
-            final float dx = mMarquee.getGhostOffset();
-            canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
-            layout.draw(canvas, mHighlightPaths, mHighlightPaints, highlight, mHighlightPaint,
-                    cursorOffsetVertical);
-        }
-
-        canvas.restore();
     }
 
     @Override
@@ -11254,192 +11260,201 @@
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
-        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
-        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
-        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "TextView.onMeasure");
+        try {
+            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+            int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+            int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+            int heightSize = MeasureSpec.getSize(heightMeasureSpec);
 
-        int width;
-        int height;
+            int width;
+            int height;
 
-        BoringLayout.Metrics boring = UNKNOWN_BORING;
-        BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
+            BoringLayout.Metrics boring = UNKNOWN_BORING;
+            BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
 
-        if (mTextDir == null) {
-            mTextDir = getTextDirectionHeuristic();
-        }
-
-        int des = -1;
-        boolean fromexisting = false;
-        final float widthLimit = (widthMode == MeasureSpec.AT_MOST)
-                ?  (float) widthSize : Float.MAX_VALUE;
-
-        if (widthMode == MeasureSpec.EXACTLY) {
-            // Parent has told us how big to be. So be it.
-            width = widthSize;
-        } else {
-            if (mLayout != null && mEllipsize == null) {
-                des = desired(mLayout, mUseBoundsForWidth);
+            if (mTextDir == null) {
+                mTextDir = getTextDirectionHeuristic();
             }
 
-            if (des < 0) {
-                boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir,
-                        isFallbackLineSpacingForBoringLayout(), getResolvedMinimumFontMetrics(),
-                        mBoring);
-                if (boring != null) {
-                    mBoring = boring;
-                }
+            int des = -1;
+            boolean fromexisting = false;
+            final float widthLimit = (widthMode == MeasureSpec.AT_MOST)
+                    ? (float) widthSize : Float.MAX_VALUE;
+
+            if (widthMode == MeasureSpec.EXACTLY) {
+                // Parent has told us how big to be. So be it.
+                width = widthSize;
             } else {
-                fromexisting = true;
-            }
+                if (mLayout != null && mEllipsize == null) {
+                    des = desired(mLayout, mUseBoundsForWidth);
+                }
 
-            if (boring == null || boring == UNKNOWN_BORING) {
                 if (des < 0) {
-                    des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0,
-                            mTransformed.length(), mTextPaint, mTextDir, widthLimit,
-                            mUseBoundsForWidth));
-                }
-                width = des;
-            } else {
-                if (mUseBoundsForWidth) {
-                    RectF bbox = boring.getDrawingBoundingBox();
-                    float rightMax = Math.max(bbox.right, boring.width);
-                    float leftMin = Math.min(bbox.left, 0);
-                    width = Math.max(boring.width, (int) Math.ceil(rightMax - leftMin));
-                } else {
-                    width = boring.width;
-                }
-            }
-
-            final Drawables dr = mDrawables;
-            if (dr != null) {
-                width = Math.max(width, dr.mDrawableWidthTop);
-                width = Math.max(width, dr.mDrawableWidthBottom);
-            }
-
-            if (mHint != null) {
-                int hintDes = -1;
-                int hintWidth;
-
-                if (mHintLayout != null && mEllipsize == null) {
-                    hintDes = desired(mHintLayout, mUseBoundsForWidth);
-                }
-
-                if (hintDes < 0) {
-                    hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
+                    boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir,
                             isFallbackLineSpacingForBoringLayout(), getResolvedMinimumFontMetrics(),
-                            mHintBoring);
-                    if (hintBoring != null) {
-                        mHintBoring = hintBoring;
+                            mBoring);
+                    if (boring != null) {
+                        mBoring = boring;
                     }
+                } else {
+                    fromexisting = true;
                 }
 
-                if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
-                    if (hintDes < 0) {
-                        hintDes = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mHint, 0,
-                                mHint.length(), mTextPaint, mTextDir, widthLimit,
+                if (boring == null || boring == UNKNOWN_BORING) {
+                    if (des < 0) {
+                        des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0,
+                                mTransformed.length(), mTextPaint, mTextDir, widthLimit,
                                 mUseBoundsForWidth));
                     }
-                    hintWidth = hintDes;
+                    width = des;
                 } else {
-                    hintWidth = hintBoring.width;
+                    if (mUseBoundsForWidth) {
+                        RectF bbox = boring.getDrawingBoundingBox();
+                        float rightMax = Math.max(bbox.right, boring.width);
+                        float leftMin = Math.min(bbox.left, 0);
+                        width = Math.max(boring.width, (int) Math.ceil(rightMax - leftMin));
+                    } else {
+                        width = boring.width;
+                    }
                 }
 
-                if (hintWidth > width) {
-                    width = hintWidth;
+                final Drawables dr = mDrawables;
+                if (dr != null) {
+                    width = Math.max(width, dr.mDrawableWidthTop);
+                    width = Math.max(width, dr.mDrawableWidthBottom);
                 }
-            }
 
-            width += getCompoundPaddingLeft() + getCompoundPaddingRight();
+                if (mHint != null) {
+                    int hintDes = -1;
+                    int hintWidth;
 
-            if (mMaxWidthMode == EMS) {
-                width = Math.min(width, mMaxWidth * getLineHeight());
-            } else {
-                width = Math.min(width, mMaxWidth);
-            }
+                    if (mHintLayout != null && mEllipsize == null) {
+                        hintDes = desired(mHintLayout, mUseBoundsForWidth);
+                    }
 
-            if (mMinWidthMode == EMS) {
-                width = Math.max(width, mMinWidth * getLineHeight());
-            } else {
-                width = Math.max(width, mMinWidth);
-            }
+                    if (hintDes < 0) {
+                        hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
+                                isFallbackLineSpacingForBoringLayout(),
+                                getResolvedMinimumFontMetrics(),
+                                mHintBoring);
+                        if (hintBoring != null) {
+                            mHintBoring = hintBoring;
+                        }
+                    }
 
-            // Check against our minimum width
-            width = Math.max(width, getSuggestedMinimumWidth());
+                    if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
+                        if (hintDes < 0) {
+                            hintDes = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mHint, 0,
+                                    mHint.length(), mTextPaint, mTextDir, widthLimit,
+                                    mUseBoundsForWidth));
+                        }
+                        hintWidth = hintDes;
+                    } else {
+                        hintWidth = hintBoring.width;
+                    }
 
-            if (widthMode == MeasureSpec.AT_MOST) {
-                width = Math.min(widthSize, width);
-            }
-        }
+                    if (hintWidth > width) {
+                        width = hintWidth;
+                    }
+                }
 
-        int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
-        int unpaddedWidth = want;
+                width += getCompoundPaddingLeft() + getCompoundPaddingRight();
 
-        if (mHorizontallyScrolling) want = VERY_WIDE;
-
-        int hintWant = want;
-        int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
-
-        if (mLayout == null) {
-            makeNewLayout(want, hintWant, boring, hintBoring,
-                          width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
-        } else {
-            final boolean layoutChanged = (mLayout.getWidth() != want) || (hintWidth != hintWant)
-                    || (mLayout.getEllipsizedWidth()
-                            != width - getCompoundPaddingLeft() - getCompoundPaddingRight());
-
-            final boolean widthChanged = (mHint == null) && (mEllipsize == null)
-                    && (want > mLayout.getWidth())
-                    && (mLayout instanceof BoringLayout
-                            || (fromexisting && des >= 0 && des <= want));
-
-            final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
-
-            if (layoutChanged || maximumChanged) {
-                if (!maximumChanged && widthChanged) {
-                    mLayout.increaseWidthTo(want);
+                if (mMaxWidthMode == EMS) {
+                    width = Math.min(width, mMaxWidth * getLineHeight());
                 } else {
-                    makeNewLayout(want, hintWant, boring, hintBoring,
-                            width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
+                    width = Math.min(width, mMaxWidth);
                 }
+
+                if (mMinWidthMode == EMS) {
+                    width = Math.max(width, mMinWidth * getLineHeight());
+                } else {
+                    width = Math.max(width, mMinWidth);
+                }
+
+                // Check against our minimum width
+                width = Math.max(width, getSuggestedMinimumWidth());
+
+                if (widthMode == MeasureSpec.AT_MOST) {
+                    width = Math.min(widthSize, width);
+                }
+            }
+
+            int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
+            int unpaddedWidth = want;
+
+            if (mHorizontallyScrolling) want = VERY_WIDE;
+
+            int hintWant = want;
+            int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
+
+            if (mLayout == null) {
+                makeNewLayout(want, hintWant, boring, hintBoring,
+                        width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
             } else {
-                // Nothing has changed
+                final boolean layoutChanged =
+                        (mLayout.getWidth() != want) || (hintWidth != hintWant)
+                                || (mLayout.getEllipsizedWidth()
+                                != width - getCompoundPaddingLeft() - getCompoundPaddingRight());
+
+                final boolean widthChanged = (mHint == null) && (mEllipsize == null)
+                        && (want > mLayout.getWidth())
+                        && (mLayout instanceof BoringLayout
+                        || (fromexisting && des >= 0 && des <= want));
+
+                final boolean maximumChanged =
+                        (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
+
+                if (layoutChanged || maximumChanged) {
+                    if (!maximumChanged && widthChanged) {
+                        mLayout.increaseWidthTo(want);
+                    } else {
+                        makeNewLayout(want, hintWant, boring, hintBoring,
+                                width - getCompoundPaddingLeft() - getCompoundPaddingRight(),
+                                false);
+                    }
+                } else {
+                    // Nothing has changed
+                }
             }
-        }
 
-        if (heightMode == MeasureSpec.EXACTLY) {
-            // Parent has told us how big to be. So be it.
-            height = heightSize;
-            mDesiredHeightAtMeasure = -1;
-        } else {
-            int desired = getDesiredHeight();
+            if (heightMode == MeasureSpec.EXACTLY) {
+                // Parent has told us how big to be. So be it.
+                height = heightSize;
+                mDesiredHeightAtMeasure = -1;
+            } else {
+                int desired = getDesiredHeight();
 
-            height = desired;
-            mDesiredHeightAtMeasure = desired;
+                height = desired;
+                mDesiredHeightAtMeasure = desired;
 
-            if (heightMode == MeasureSpec.AT_MOST) {
-                height = Math.min(desired, heightSize);
+                if (heightMode == MeasureSpec.AT_MOST) {
+                    height = Math.min(desired, heightSize);
+                }
             }
-        }
 
-        int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
-        if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
-            unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
-        }
+            int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
+            if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
+                unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
+            }
 
-        /*
-         * We didn't let makeNewLayout() register to bring the cursor into view,
-         * so do it here if there is any possibility that it is needed.
-         */
-        if (mMovement != null
-                || mLayout.getWidth() > unpaddedWidth
-                || mLayout.getHeight() > unpaddedHeight) {
-            registerForPreDraw();
-        } else {
-            scrollTo(0, 0);
-        }
+            /*
+             * We didn't let makeNewLayout() register to bring the cursor into view,
+             * so do it here if there is any possibility that it is needed.
+             */
+            if (mMovement != null
+                    || mLayout.getWidth() > unpaddedWidth
+                    || mLayout.getHeight() > unpaddedHeight) {
+                registerForPreDraw();
+            } else {
+                scrollTo(0, 0);
+            }
 
-        setMeasuredDimension(width, height);
+            setMeasuredDimension(width, height);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+        }
     }
 
     /**
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 0f2dd10..2c21417 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -49,6 +49,7 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.hardware.HardwareBuffer;
+import android.os.BinderProxy;
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -1089,8 +1090,13 @@
         @Override
         public String toString() {
             final StringBuilder sb = new StringBuilder();
-            sb.append('{'); sb.append(mContainer);
-            sb.append(" m="); sb.append(modeToString(mMode));
+            sb.append('{');
+            if (mContainer != null && !(mContainer.asBinder() instanceof BinderProxy)) {
+                // Only log the token if it is not a binder proxy and has additional container info
+                sb.append(mContainer);
+                sb.append(" ");
+            }
+            sb.append("m="); sb.append(modeToString(mMode));
             sb.append(" f="); sb.append(flagsToString(mFlags));
             if (mParent != null) {
                 sb.append(" p="); sb.append(mParent);
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 3fe63ab..a88a172 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -1120,8 +1120,8 @@
     @NonNull
     public String toString() {
         return "WindowContainerTransaction {"
-                + " changes = " + mChanges
-                + " hops = " + mHierarchyOps
+                + " changes= " + mChanges
+                + " hops= " + mHierarchyOps
                 + " errorCallbackToken=" + mErrorCallbackToken
                 + " taskFragmentOrganizer=" + mTaskFragmentOrganizer
                 + " }";
diff --git a/core/jni/android_hardware_OverlayProperties.cpp b/core/jni/android_hardware_OverlayProperties.cpp
index bb4084e..f64dec8 100644
--- a/core/jni/android_hardware_OverlayProperties.cpp
+++ b/core/jni/android_hardware_OverlayProperties.cpp
@@ -106,7 +106,7 @@
                                                                         jlong nativeObject) {
     gui::OverlayProperties* overlayProperties =
             reinterpret_cast<gui::OverlayProperties*>(nativeObject);
-    if (overlayProperties->lutProperties.has_value()) {
+    if (!overlayProperties || !overlayProperties->lutProperties) {
         return NULL;
     }
     auto& lutProperties = overlayProperties->lutProperties.value();
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 49191ee..7ef7829 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -33,6 +33,7 @@
 
 #include <algorithm>
 #include <array>
+#include <cctype>
 #include <cstring>
 #include <limits>
 #include <memory>
@@ -1008,6 +1009,8 @@
                 }
             }
             if ((mode&PROC_OUT_STRING) != 0 && di < NS) {
+                std::replace_if(buffer+start, buffer+end,
+                                [](unsigned char c){ return !std::isprint(c); }, '?');
                 jstring str = env->NewStringUTF(buffer+start);
                 env->SetObjectArrayElement(outStrings, di, str);
             }
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 82b463e..450b88b 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -758,54 +758,64 @@
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
     SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
 
-    ScopedIntArrayRW joffsets(env, joffsetArray);
-    if (joffsets.get() == nullptr) {
-        jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from joffsetArray");
-        return;
-    }
-    ScopedIntArrayRW jdimensions(env, jdimensionArray);
-    if (jdimensions.get() == nullptr) {
-        jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jdimensionArray");
-        return;
-    }
-    ScopedIntArrayRW jsizes(env, jsizeArray);
-    if (jsizes.get() == nullptr) {
-        jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jsizeArray");
-        return;
-    }
-    ScopedIntArrayRW jsamplingKeys(env, jsamplingKeyArray);
-    if (jsamplingKeys.get() == nullptr) {
-        jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jsamplingKeyArray");
-        return;
-    }
+    std::vector<int32_t> offsets;
+    std::vector<int32_t> dimensions;
+    std::vector<int32_t> sizes;
+    std::vector<int32_t> samplingKeys;
+    int32_t fd = -1;
 
-    jsize numLuts = env->GetArrayLength(jdimensionArray);
-    std::vector<int32_t> offsets(joffsets.get(), joffsets.get() + numLuts);
-    std::vector<int32_t> dimensions(jdimensions.get(), jdimensions.get() + numLuts);
-    std::vector<int32_t> sizes(jsizes.get(), jsizes.get() + numLuts);
-    std::vector<int32_t> samplingKeys(jsamplingKeys.get(), jsamplingKeys.get() + numLuts);
+    if (jdimensionArray) {
+        jsize numLuts = env->GetArrayLength(jdimensionArray);
+        ScopedIntArrayRW joffsets(env, joffsetArray);
+        if (joffsets.get() == nullptr) {
+            jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from joffsetArray");
+            return;
+        }
+        ScopedIntArrayRW jdimensions(env, jdimensionArray);
+        if (jdimensions.get() == nullptr) {
+            jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jdimensionArray");
+            return;
+        }
+        ScopedIntArrayRW jsizes(env, jsizeArray);
+        if (jsizes.get() == nullptr) {
+            jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jsizeArray");
+            return;
+        }
+        ScopedIntArrayRW jsamplingKeys(env, jsamplingKeyArray);
+        if (jsamplingKeys.get() == nullptr) {
+            jniThrowRuntimeException(env, "Failed to get ScopedIntArrayRW from jsamplingKeyArray");
+            return;
+        }
 
-    ScopedFloatArrayRW jbuffers(env, jbufferArray);
-    if (jbuffers.get() == nullptr) {
-        jniThrowRuntimeException(env, "Failed to get ScopedFloatArrayRW from jbufferArray");
-        return;
-    }
+        if (numLuts > 0) {
+            offsets = std::vector<int32_t>(joffsets.get(), joffsets.get() + numLuts);
+            dimensions = std::vector<int32_t>(jdimensions.get(), jdimensions.get() + numLuts);
+            sizes = std::vector<int32_t>(jsizes.get(), jsizes.get() + numLuts);
+            samplingKeys = std::vector<int32_t>(jsamplingKeys.get(), jsamplingKeys.get() + numLuts);
 
-    // create the shared memory and copy jbuffers
-    size_t bufferSize = jbuffers.size() * sizeof(float);
-    int32_t fd = ashmem_create_region("lut_shread_mem", bufferSize);
-    if (fd < 0) {
-        jniThrowRuntimeException(env, "ashmem_create_region() failed");
-        return;
+            ScopedFloatArrayRW jbuffers(env, jbufferArray);
+            if (jbuffers.get() == nullptr) {
+                jniThrowRuntimeException(env, "Failed to get ScopedFloatArrayRW from jbufferArray");
+                return;
+            }
+
+            // create the shared memory and copy jbuffers
+            size_t bufferSize = jbuffers.size() * sizeof(float);
+            fd = ashmem_create_region("lut_shared_mem", bufferSize);
+            if (fd < 0) {
+                jniThrowRuntimeException(env, "ashmem_create_region() failed");
+                return;
+            }
+            void* ptr = mmap(nullptr, bufferSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+            if (ptr == MAP_FAILED) {
+                jniThrowRuntimeException(env, "Failed to map the shared memory");
+                return;
+            }
+            memcpy(ptr, jbuffers.get(), bufferSize);
+            // unmap
+            munmap(ptr, bufferSize);
+        }
     }
-    void* ptr = mmap(nullptr, bufferSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
-    if (ptr == MAP_FAILED) {
-        jniThrowRuntimeException(env, "Failed to map the shared memory");
-        return;
-    }
-    memcpy(ptr, jbuffers.get(), bufferSize);
-    // unmap
-    munmap(ptr, bufferSize);
 
     transaction->setLuts(ctrl, base::unique_fd(fd), offsets, dimensions, sizes, samplingKeys);
 }
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 41dec37..7ef5394 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1855,13 +1855,23 @@
          {@link android.R.styleable#AndroidManifestProcess process} tag, or to an
          {@link android.R.styleable#AndroidManifestApplication application} tag (to supply
          a default setting for all application components). -->
-    <attr name="memtagMode">
+     <attr name="memtagMode">
        <enum name="default" value="-1" />
        <enum name="off" value="0" />
        <enum name="async" value="1" />
        <enum name="sync" value="2" />
     </attr>
 
+    <!-- This attribute will be used to override app compatibility mode on 16 KB devices.
+         If set to enabled, Natives lib will be extracted from APK if they are not page aligned on
+         16 KB device. 4 KB natives libs will be loaded app-compat mode if they are eligible.
+         @FlaggedApi(android.content.pm.Flags.FLAG_APP_COMPAT_OPTION_16KB) -->
+    <attr name="pageSizeCompat">
+        <enum name="enabled" value="5" />
+        <enum name="disabled" value="6" />
+    </attr>
+
+
     <!-- Attribution tag to be used for permission sub-attribution if a
       permission is checked in  {@link android.content.Context#sendBroadcast(Intent, String)}.
       Multiple tags can be specified separated by '|'.
@@ -2212,6 +2222,9 @@
 
         <attr name="memtagMode" />
 
+        <!-- @FlaggedApi(android.content.pm.Flags.FLAG_APP_COMPAT_OPTION_16KB) -->
+        <attr name="pageSizeCompat" />
+
         <!-- If {@code true} enables automatic zero initialization of all native heap
              allocations. -->
         <attr name="nativeHeapZeroInitialized" format="boolean" />
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index b6436d0..a0bf89d 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -131,6 +131,8 @@
     <public name="alternateLauncherIcons"/>
     <!-- @FlaggedApi(android.content.pm.Flags.FLAG_CHANGE_LAUNCHER_BADGING) -->
     <public name="alternateLauncherLabels"/>
+    <!-- @FlaggedApi(android.content.pm.Flags.FLAG_APP_COMPAT_OPTION_16KB) -->
+    <public name="pageSizeCompat" />
   </staging-public-group>
 
   <staging-public-group type="id" first-id="0x01b60000">
diff --git a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java
index 9dd196d..f62d420 100644
--- a/core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java
@@ -507,7 +507,8 @@
                 + "prefix: supportedHandwritingGestureTypes=(none)\n"
                 + "prefix: supportedHandwritingGesturePreviewTypes=(none)\n"
                 + "prefix: isStylusHandwritingEnabled=false\n"
-                + "prefix: contentMimeTypes=null\n");
+                + "prefix: contentMimeTypes=null\n"
+                + "prefix: writingToolsEnabled=true\n");
     }
 
     @Test
@@ -539,6 +540,7 @@
         info.hintLocales = LocaleList.forLanguageTags("en,es,zh");
         info.contentMimeTypes = new String[] {"image/png"};
         info.targetInputMethodUser = UserHandle.of(10);
+        info.setWritingToolsEnabled(false);
         final StringBuilder sb = new StringBuilder();
         info.dump(new StringBuilderPrinter(sb), "prefix2: ");
         assertThat(sb.toString()).isEqualTo(
@@ -555,7 +557,8 @@
                         + "prefix2: supportedHandwritingGesturePreviewTypes=SELECT\n"
                         + "prefix2: isStylusHandwritingEnabled=" + isStylusHandwritingEnabled + "\n"
                         + "prefix2: contentMimeTypes=[image/png]\n"
-                        + "prefix2: targetInputMethodUserId=10\n");
+                        + "prefix2: targetInputMethodUserId=10\n"
+                        + "prefix2: writingToolsEnabled=false\n");
     }
 
     @Test
@@ -576,7 +579,8 @@
                         + "prefix: supportedHandwritingGestureTypes=(none)\n"
                         + "prefix: supportedHandwritingGesturePreviewTypes=(none)\n"
                         + "prefix: isStylusHandwritingEnabled=false\n"
-                        + "prefix: contentMimeTypes=null\n");
+                        + "prefix: contentMimeTypes=null\n"
+                        + "prefix: writingToolsEnabled=true\n");
     }
 
     @Test
@@ -621,4 +625,9 @@
         infoCopy.extras.putString("testKey2", "testValue");
         assertFalse(TEST_EDITOR_INFO.kindofEquals(infoCopy));
     }
+
+    @Test
+    public void testWritingToolsEnabledbyDefault() {
+        assertTrue(TEST_EDITOR_INFO.isWritingToolsEnabled());
+    }
 }
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
index 9552c88..6a5224d 100644
--- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
+++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
@@ -16,6 +16,11 @@
 
 package android.hardware.display;
 
+import static android.hardware.display.DisplayManagerGlobal.EVENT_DISPLAY_STATE_CHANGED;
+import static android.hardware.display.DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE;
+import static android.hardware.display.DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_STATE;
+
+import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
@@ -28,13 +33,19 @@
 import android.os.Handler;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.view.DisplayInfo;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.server.display.feature.flags.Flags;
+
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -55,6 +66,10 @@
 @RunWith(AndroidJUnit4.class)
 public class DisplayManagerGlobalTest {
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+
     private static final long ALL_DISPLAY_EVENTS =
             DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
             | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED
@@ -117,6 +132,33 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DISPLAY_LISTENER_PERFORMANCE_IMPROVEMENTS)
+    public void testDisplayListenerIsCalled_WhenDisplayPropertyChangeEventOccurs()
+            throws RemoteException {
+        mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
+                INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE
+                        | INTERNAL_EVENT_FLAG_DISPLAY_STATE,
+                null);
+        Mockito.verify(mDisplayManager)
+                .registerCallbackWithEventMask(mCallbackCaptor.capture(), anyLong());
+        IDisplayManagerCallback callback = mCallbackCaptor.getValue();
+
+        int displayId = 1;
+
+        Mockito.reset(mListener);
+        callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REFRESH_RATE_CHANGED);
+        waitForHandler();
+        Mockito.verify(mListener).onDisplayChanged(eq(displayId));
+        Mockito.verifyNoMoreInteractions(mListener);
+
+        Mockito.reset(mListener);
+        callback.onDisplayEvent(displayId, EVENT_DISPLAY_STATE_CHANGED);
+        waitForHandler();
+        Mockito.verify(mListener).onDisplayChanged(eq(displayId));
+        Mockito.verifyNoMoreInteractions(mListener);
+    }
+
+    @Test
     public void testDisplayListenerIsNotCalled_WhenClientIsNotSubscribed() throws RemoteException {
         // First we subscribe to all events in order to test that the subsequent calls to
         // registerDisplayListener will update the event mask.
@@ -231,6 +273,53 @@
         verify(mListener2, never()).onDisplayChanged(anyInt());
     }
 
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DISPLAY_LISTENER_PERFORMANCE_IMPROVEMENTS)
+    public void testMapFlagsToInternalEventFlag() {
+        // Test public flags mapping
+        assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED,
+                mDisplayManagerGlobal
+                        .mapFlagsToInternalEventFlag(DisplayManager.EVENT_FLAG_DISPLAY_ADDED, 0));
+        assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED,
+                mDisplayManagerGlobal
+                        .mapFlagsToInternalEventFlag(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED, 0));
+        assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED,
+                mDisplayManagerGlobal
+                        .mapFlagsToInternalEventFlag(DisplayManager.EVENT_FLAG_DISPLAY_REMOVED, 0));
+        assertEquals(INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE,
+                mDisplayManagerGlobal
+                        .mapFlagsToInternalEventFlag(
+                                DisplayManager.EVENT_FLAG_DISPLAY_REFRESH_RATE,
+                                0));
+        assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_STATE,
+                mDisplayManagerGlobal
+                        .mapFlagsToInternalEventFlag(
+                                DisplayManager.EVENT_FLAG_DISPLAY_STATE,
+                                0));
+
+        // test private flags mapping
+        assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED,
+                mDisplayManagerGlobal
+                        .mapFlagsToInternalEventFlag(0,
+                                DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED));
+        assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED,
+                mDisplayManagerGlobal
+                        .mapFlagsToInternalEventFlag(0,
+                                DisplayManager.PRIVATE_EVENT_FLAG_HDR_SDR_RATIO_CHANGED));
+        assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED,
+                mDisplayManagerGlobal
+                        .mapFlagsToInternalEventFlag(0,
+                                DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
+
+        // Test both public and private flags mapping
+        assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED
+                        | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE,
+                mDisplayManagerGlobal
+                        .mapFlagsToInternalEventFlag(
+                                DisplayManager.EVENT_FLAG_DISPLAY_REFRESH_RATE,
+                                DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
+    }
+
     private void waitForHandler() {
         mHandler.runWithScissors(() -> {
         }, 0);
diff --git a/core/tests/coretests/src/android/os/OWNERS b/core/tests/coretests/src/android/os/OWNERS
index 6149382..4620cb8 100644
--- a/core/tests/coretests/src/android/os/OWNERS
+++ b/core/tests/coretests/src/android/os/OWNERS
@@ -12,3 +12,6 @@
 
 # Caching
 per-file IpcDataCache* = file:/PERFORMANCE_OWNERS
+
+# RemoteCallbackList
+per-file RemoteCallbackListTest.java = shayba@google.com
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java
index 03e0ab0..a31acc8 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java
@@ -249,9 +249,10 @@
             return null;
         }
         return "id=" + taskInfo.taskId
-                + " baseIntent=" + (taskInfo.baseIntent != null
-                        ? taskInfo.baseIntent.getComponent()
-                        : "null")
+                + " baseIntent=" +
+                        (taskInfo.baseIntent != null && taskInfo.baseIntent.getComponent() != null
+                                ? taskInfo.baseIntent.getComponent().flattenToString()
+                                : "null")
                 + " winMode=" + WindowConfiguration.windowingModeToString(
                         taskInfo.getWindowingMode());
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 39dc267..823c533 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -274,8 +274,11 @@
     private final DragAndDropController mDragAndDropController;
     /** Used to send bubble events to launcher. */
     private Bubbles.BubbleStateListener mBubbleStateListener;
-    /** Used to track previous navigation mode to detect switch to buttons navigation. */
-    private boolean mIsPrevNavModeGestures;
+    /**
+     * Used to track previous navigation mode to detect switch to buttons navigation. Set to
+     * true to switch the bubble bar to the opposite side for 3 nav buttons mode on device boot.
+     */
+    private boolean mIsPrevNavModeGestures = true;
     /** Used to send updates to the views from {@link #mBubbleDataListener}. */
     private BubbleViewCallback mBubbleViewCallback;
 
@@ -357,7 +360,6 @@
             }
         };
         mExpandedViewManager = BubbleExpandedViewManager.fromBubbleController(this);
-        mIsPrevNavModeGestures = ContextUtils.isGestureNavigationMode(mContext);
     }
 
     private void registerOneHandedState(OneHandedController oneHanded) {
@@ -593,9 +595,9 @@
         if (mBubbleStateListener != null) {
             boolean isCurrentNavModeGestures = ContextUtils.isGestureNavigationMode(mContext);
             if (mIsPrevNavModeGestures && !isCurrentNavModeGestures) {
-                BubbleBarLocation navButtonsLocation = ContextUtils.isRtl(mContext)
+                BubbleBarLocation bubbleBarLocation = ContextUtils.isRtl(mContext)
                         ? BubbleBarLocation.RIGHT : BubbleBarLocation.LEFT;
-                mBubblePositioner.setBubbleBarLocation(navButtonsLocation);
+                mBubblePositioner.setBubbleBarLocation(bubbleBarLocation);
             }
             mIsPrevNavModeGestures = isCurrentNavModeGestures;
             BubbleBarUpdate update = mBubbleData.getInitialStateForBubbleBar();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 9911669..cea4d33 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -205,7 +205,7 @@
         mTaskSplitBoundsMap.put(taskId1, splitBounds);
         mTaskSplitBoundsMap.put(taskId2, splitBounds);
         notifyRecentTasksChanged();
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENT_TASKS, "Add split pair: %d, %d, %s",
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Add split pair: %d, %d, %s",
                 taskId1, taskId2, splitBounds);
         return true;
     }
@@ -221,7 +221,7 @@
             mTaskSplitBoundsMap.remove(taskId);
             mTaskSplitBoundsMap.remove(pairedTaskId);
             notifyRecentTasksChanged();
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENT_TASKS, "Remove split pair: %d, %d",
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Remove split pair: %d, %d",
                     taskId, pairedTaskId);
         }
     }
@@ -234,7 +234,17 @@
 
         // We could do extra verification of requiring both taskIds of a pair and verifying that
         // the same split bounds object is returned... but meh. Seems unnecessary.
-        return mTaskSplitBoundsMap.get(taskId);
+        SplitBounds splitBounds = mTaskSplitBoundsMap.get(taskId);
+        if (splitBounds != null) {
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+                    "getSplitBoundsForTaskId: taskId=%d splitBoundsTasks=[%d, %d]", taskId,
+                    splitBounds.leftTopTaskId, splitBounds.rightBottomTaskId);
+        } else {
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+                    "getSplitBoundsForTaskId: expected split bounds for taskId=%d but not found",
+                    taskId);
+        }
+        return splitBounds;
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index cc0e1df..7d1ffb8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -1362,7 +1362,11 @@
     }
 
     void clearSplitPairedInRecents(@ExitReason int exitReason) {
-        if (!shouldBreakPairedTaskInRecents(exitReason) || !mShouldUpdateRecents) return;
+        if (!shouldBreakPairedTaskInRecents(exitReason) || !mShouldUpdateRecents) {
+            ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "clearSplitPairedInRecents: skipping reason=%s",
+                    !mShouldUpdateRecents ? "shouldn't update" : exitReasonToString(exitReason));
+            return;
+        }
 
         ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "clearSplitPairedInRecents: reason=%s",
                 exitReasonToString(exitReason));
@@ -1608,6 +1612,8 @@
     private void updateRecentTasksSplitPair() {
         // Preventing from single task update while processing recents.
         if (!mShouldUpdateRecents || !mPausingTasks.isEmpty()) {
+            ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "updateRecentTasksSplitPair: skipping reason=%s",
+                    !mShouldUpdateRecents ? "shouldn't update" : "no pausing tasks");
             return;
         }
         mRecentTasks.ifPresent(recentTasks -> {
diff --git a/media/java/android/media/AudioPlaybackConfiguration.java b/media/java/android/media/AudioPlaybackConfiguration.java
index 3cd5f52..da50f2c 100644
--- a/media/java/android/media/AudioPlaybackConfiguration.java
+++ b/media/java/android/media/AudioPlaybackConfiguration.java
@@ -19,6 +19,7 @@
 import static android.media.AudioAttributes.ALLOW_CAPTURE_BY_ALL;
 import static android.media.AudioAttributes.ALLOW_CAPTURE_BY_NONE;
 import static android.media.audio.Flags.FLAG_MUTED_BY_PORT_VOLUME_API;
+import static android.media.audio.Flags.FLAG_ROUTED_DEVICE_IDS;
 
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
@@ -39,6 +40,8 @@
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -461,8 +464,12 @@
 
     /**
      * Returns information about the {@link AudioDeviceInfo} used for this playback.
-     * @return the audio playback device or null if the device is not available at the time of query
+     * @return the audio playback device or null if the device is not available at the time of
+     * query.
+     * @deprecated this information was never populated
      */
+    @Deprecated
+    @FlaggedApi(FLAG_ROUTED_DEVICE_IDS)
     public @Nullable AudioDeviceInfo getAudioDeviceInfo() {
         final int deviceId;
         synchronized (mUpdateablePropLock) {
@@ -476,6 +483,23 @@
 
     /**
      * @hide
+     * Returns information about the List of {@link AudioDeviceInfo} used for this playback.
+     * @return the audio playback devices
+     */
+    @SystemApi
+    @FlaggedApi(FLAG_ROUTED_DEVICE_IDS)
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public @NonNull List<AudioDeviceInfo> getAudioDeviceInfos() {
+        List<AudioDeviceInfo> audioDeviceInfos = new ArrayList<AudioDeviceInfo>();
+        AudioDeviceInfo audioDeviceInfo = getAudioDeviceInfo();
+        if (audioDeviceInfo != null) {
+            audioDeviceInfos.add(audioDeviceInfo);
+        }
+        return audioDeviceInfos;
+    }
+
+    /**
+     * @hide
      * Return the audio session ID associated with this player.
      * See {@link AudioManager#generateAudioSessionId()}.
      * @return an audio session ID
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index 80e5719..9394941 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -20,8 +20,10 @@
 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
 import static android.content.Context.DEVICE_ID_DEFAULT;
 import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
+import static android.media.audio.Flags.FLAG_ROUTED_DEVICE_IDS;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
@@ -1920,6 +1922,23 @@
     }
 
     /**
+     * Returns a List of {@link AudioDeviceInfo} identifying the current routing of this
+     * AudioRecord.
+     * Note: The query is only valid if the AudioRecord is currently playing. If it is not,
+     * <code>getRoutedDevices()</code> will return an empty list.
+     */
+    @Override
+    @FlaggedApi(FLAG_ROUTED_DEVICE_IDS)
+    public @NonNull List<AudioDeviceInfo> getRoutedDevices() {
+        List<AudioDeviceInfo> audioDeviceInfos = new ArrayList<AudioDeviceInfo>();
+        AudioDeviceInfo audioDeviceInfo = getRoutedDevice();
+        if (audioDeviceInfo != null) {
+            audioDeviceInfos.add(audioDeviceInfo);
+        }
+        return audioDeviceInfos;
+    }
+
+    /**
      * Must match the native definition in frameworks/av/service/audioflinger/Audioflinger.h.
      */
     private static final long MAX_SHARED_AUDIO_HISTORY_MS = 5000;
diff --git a/media/java/android/media/AudioRouting.java b/media/java/android/media/AudioRouting.java
index 26fa631..22aa9a0 100644
--- a/media/java/android/media/AudioRouting.java
+++ b/media/java/android/media/AudioRouting.java
@@ -16,9 +16,16 @@
 
 package android.media;
 
+import static android.media.audio.Flags.FLAG_ROUTED_DEVICE_IDS;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
 import android.os.Handler;
 import android.os.Looper;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * AudioRouting defines an interface for controlling routing and routing notifications in
  * AudioTrack and AudioRecord objects.
@@ -49,6 +56,22 @@
     public AudioDeviceInfo getRoutedDevice();
 
     /**
+     * Returns a List of {@link AudioDeviceInfo} identifying the current routing of this
+     * AudioTrack/AudioRecord.
+     * Note: The query is only valid if the AudioTrack/AudioRecord is currently playing.
+     * If it is not, <code>getRoutedDevices()</code> will return an empty List.
+     */
+    @FlaggedApi(FLAG_ROUTED_DEVICE_IDS)
+    default @NonNull List<AudioDeviceInfo> getRoutedDevices() {
+        List<AudioDeviceInfo> audioDeviceInfos = new ArrayList<AudioDeviceInfo>();
+        AudioDeviceInfo audioDeviceInfo = getRoutedDevice();
+        if (audioDeviceInfo != null) {
+            audioDeviceInfos.add(audioDeviceInfo);
+        }
+        return new ArrayList<AudioDeviceInfo>();
+    }
+
+    /**
      * Adds an {@link AudioRouting.OnRoutingChangedListener} to receive notifications of routing
      * changes on this AudioTrack/AudioRecord.
      * @param listener The {@link AudioRouting.OnRoutingChangedListener} interface to receive
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 03cd535..93a1831 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -17,8 +17,10 @@
 package android.media;
 
 import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
+import static android.media.audio.Flags.FLAG_ROUTED_DEVICE_IDS;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
@@ -54,7 +56,9 @@
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.nio.NioUtils;
+import java.util.ArrayList;
 import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.Executor;
@@ -3783,6 +3787,8 @@
      * Returns an {@link AudioDeviceInfo} identifying the current routing of this AudioTrack.
      * Note: The query is only valid if the AudioTrack is currently playing. If it is not,
      * <code>getRoutedDevice()</code> will return null.
+     * Audio may play on multiple devices simultaneously (e.g. an alarm playing on headphones and
+     * speaker on a phone), so prefer using {@link #getRoutedDevices}.
      */
     @Override
     public AudioDeviceInfo getRoutedDevice() {
@@ -3793,6 +3799,23 @@
         return AudioManager.getDeviceForPortId(deviceId, AudioManager.GET_DEVICES_OUTPUTS);
     }
 
+    /**
+     * Returns a List of {@link AudioDeviceInfo} identifying the current routing of this
+     * AudioTrack.
+     * Note: The query is only valid if the AudioTrack is currently playing. If it is not,
+     * <code>getRoutedDevices()</code> will return an empty list.
+     */
+    @Override
+    @FlaggedApi(FLAG_ROUTED_DEVICE_IDS)
+    public @NonNull List<AudioDeviceInfo> getRoutedDevices() {
+        List<AudioDeviceInfo> audioDeviceInfos = new ArrayList<AudioDeviceInfo>();
+        AudioDeviceInfo audioDeviceInfo = getRoutedDevice();
+        if (audioDeviceInfo != null) {
+            audioDeviceInfos.add(audioDeviceInfo);
+        }
+        return audioDeviceInfos;
+    }
+
     private void tryToDisableNativeRoutingCallback() {
         synchronized (mRoutingChangeListeners) {
             if (mEnableSelfRoutingMonitor) {
diff --git a/media/java/android/media/MediaCas.java b/media/java/android/media/MediaCas.java
index 3f9126a..1ecba31 100644
--- a/media/java/android/media/MediaCas.java
+++ b/media/java/android/media/MediaCas.java
@@ -16,10 +16,9 @@
 
 package android.media;
 
+import static android.media.tv.flags.Flags.FLAG_MEDIACAS_UPDATE_CLIENT_PROFILE_PRIORITY;
 import static android.media.tv.flags.Flags.FLAG_SET_RESOURCE_HOLDER_RETAIN;
 
-import static com.android.media.flags.Flags.FLAG_UPDATE_CLIENT_PROFILE_PRIORITY;
-
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -996,7 +995,7 @@
      * @param niceValue the nice value.
      * @hide
      */
-    @FlaggedApi(FLAG_UPDATE_CLIENT_PROFILE_PRIORITY)
+    @FlaggedApi(FLAG_MEDIACAS_UPDATE_CLIENT_PROFILE_PRIORITY)
     @SystemApi
     @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS)
     public boolean updateResourcePriority(int priority, int niceValue) {
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index a0f8ae5..158bc7f 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -18,7 +18,9 @@
 
 import static android.Manifest.permission.BIND_IMS_SERVICE;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.media.audio.Flags.FLAG_ROUTED_DEVICE_IDS;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -84,6 +86,7 @@
 import java.net.InetSocketAddress;
 import java.net.URL;
 import java.nio.ByteOrder;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.BitSet;
 import java.util.HashMap;
@@ -1542,6 +1545,8 @@
      * Note: The query is only valid if the MediaPlayer is currently playing.
      * If the player is not playing, the returned device can be null or correspond to previously
      * selected device when the player was last active.
+     * Audio may play on multiple devices simultaneously (e.g. an alarm playing on headphones and
+     * speaker on a phone), so prefer using {@link #getRoutedDevices}.
      */
     @Override
     public AudioDeviceInfo getRoutedDevice() {
@@ -1552,6 +1557,23 @@
         return AudioManager.getDeviceForPortId(deviceId, AudioManager.GET_DEVICES_OUTPUTS);
     }
 
+    /**
+     * Returns a List of {@link AudioDeviceInfo} identifying the current routing of this
+     * MediaPlayer.
+     * Note: The query is only valid if the MediaPlayer is currently playing.
+     * If the player is not playing, the returned devices can be empty or correspond to previously
+     * selected devices when the player was last active.
+     */
+    @Override
+    @FlaggedApi(FLAG_ROUTED_DEVICE_IDS)
+    public @NonNull List<AudioDeviceInfo> getRoutedDevices() {
+        List<AudioDeviceInfo> audioDeviceInfos = new ArrayList<AudioDeviceInfo>();
+        AudioDeviceInfo audioDeviceInfo = getRoutedDevice();
+        if (audioDeviceInfo != null) {
+            audioDeviceInfos.add(audioDeviceInfo);
+        }
+        return audioDeviceInfos;
+    }
 
     /**
      * Sends device list change notification to all listeners.
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 2d17bf5..f75bcf3 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -16,7 +16,10 @@
 
 package android.media;
 
+import static android.media.audio.Flags.FLAG_ROUTED_DEVICE_IDS;
+
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -1695,6 +1698,24 @@
         return AudioManager.getDeviceForPortId(deviceId, AudioManager.GET_DEVICES_INPUTS);
     }
 
+    /**
+     * Returns a List of {@link AudioDeviceInfo} identifying the current routing of this
+     * MediaRecorder.
+     * Note: The query is only valid if the MediaRecorder is currently recording.
+     * If the recorder is not recording, the returned devices can be empty or correspond to
+     * previously selected devices when the recorder was last active.
+     */
+    @Override
+    @FlaggedApi(FLAG_ROUTED_DEVICE_IDS)
+    public @NonNull List<AudioDeviceInfo> getRoutedDevices() {
+        List<AudioDeviceInfo> audioDeviceInfos = new ArrayList<AudioDeviceInfo>();
+        AudioDeviceInfo audioDeviceInfo = getRoutedDevice();
+        if (audioDeviceInfo != null) {
+            audioDeviceInfos.add(audioDeviceInfo);
+        }
+        return audioDeviceInfos;
+    }
+
     /*
      * Call BEFORE adding a routing callback handler or AFTER removing a routing callback handler.
      */
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 7895eb2..5b1ea8b 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -78,13 +78,6 @@
 }
 
 flag {
-    name: "update_client_profile_priority"
-    namespace: "media_solutions"
-    description : "Feature flag to add updateResourcePriority api to MediaCas"
-    bug: "300565729"
-}
-
-flag {
      name: "enable_built_in_speaker_route_suitability_statuses"
      is_exported: true
      namespace: "media_solutions"
diff --git a/media/java/android/media/tv/ad/TvAdView.java b/media/java/android/media/tv/ad/TvAdView.java
index dd2a534..ff0279f 100644
--- a/media/java/android/media/tv/ad/TvAdView.java
+++ b/media/java/android/media/tv/ad/TvAdView.java
@@ -240,6 +240,38 @@
         }
     }
 
+    /**
+     * Controls whether the TvAdView's surface is placed on top of other regular surface views in
+     * the window (but still behind the window itself).
+     *
+     * <p>Calling this overrides any previous call to {@link #setZOrderOnTop}.
+     *
+     * @param isMediaOverlay {@code true} to be on top of another regular surface, {@code false}
+     *            otherwise.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
+    public void setZOrderMediaOverlay(boolean isMediaOverlay) {
+        if (mSurfaceView != null) {
+            mSurfaceView.setZOrderOnTop(false);
+            mSurfaceView.setZOrderMediaOverlay(isMediaOverlay);
+        }
+    }
+
+    /**
+     * Controls whether the TvAdView's surface is placed on top of its window.
+     *
+     * <p>Calling this overrides any previous call to {@link #setZOrderMediaOverlay}.
+     *
+     * @param onTop {@code true} to be on top of its window, {@code false} otherwise.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
+    public void setZOrderOnTop(boolean onTop) {
+        if (mSurfaceView != null) {
+            mSurfaceView.setZOrderMediaOverlay(false);
+            mSurfaceView.setZOrderOnTop(onTop);
+        }
+    }
+
     private void resetSurfaceView() {
         if (mSurfaceView != null) {
             mSurfaceView.getHolder().removeCallback(mSurfaceHolderCallback);
diff --git a/media/java/android/media/tv/flags/media_tv.aconfig b/media/java/android/media/tv/flags/media_tv.aconfig
index 6441652..4b832ae 100644
--- a/media/java/android/media/tv/flags/media_tv.aconfig
+++ b/media/java/android/media/tv/flags/media_tv.aconfig
@@ -2,6 +2,22 @@
 container: "system"
 
 flag {
+    name: "enable_le_audio_broadcast_ui"
+    is_exported: true
+    namespace: "media_tv"
+    description: "Enable Broadcast UI for LE Audio on TV."
+    bug: "378732734"
+}
+
+flag {
+    name: "enable_le_audio_unicast_ui"
+    is_exported: true
+    namespace: "media_tv"
+    description: "Enable Unicast UI for LE Audio on TV."
+    bug: "378732734"
+}
+
+flag {
     name: "broadcast_visibility_types"
     is_exported: true
     namespace: "media_tv"
@@ -77,11 +93,19 @@
     name: "set_resource_holder_retain"
     is_exported: true
     namespace: "media_tv"
-    description : "Feature flag to add setResourceHolderRetain api to MediaCas and Tuner JAVA."
+    description: "Feature flag to add setResourceHolderRetain api to MediaCas and Tuner JAVA."
     bug: "372973197"
 }
 
 flag {
+    name: "mediacas_update_client_profile_priority"
+    is_exported: true
+    namespace: "media_tv"
+    description: "Feature flag to add updateResourcePriority api to MediaCas"
+    bug: "372971241"
+}
+
+flag {
     name: "apply_picture_profiles"
     is_exported: true
     namespace: "media_tv"
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 635572d..9e9699f 100644
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -271,6 +271,38 @@
         }
     }
 
+    /**
+     * Controls whether the TvInteractiveAppView's surface is placed on top of other regular surface
+     * views in the window (but still behind the window itself).
+     *
+     * <p>Calling this overrides any previous call to {@link #setZOrderOnTop}.
+     *
+     * @param isMediaOverlay {@code true} to be on top of another regular surface, {@code false}
+     *            otherwise.
+     */
+    @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
+    public void setZOrderMediaOverlay(boolean isMediaOverlay) {
+        if (mSurfaceView != null) {
+            mSurfaceView.setZOrderOnTop(false);
+            mSurfaceView.setZOrderMediaOverlay(isMediaOverlay);
+        }
+    }
+
+    /**
+     * Controls whether the TvInterActiveAppView's surface is placed on top of its window.
+     *
+     * <p>Calling this overrides any previous call to {@link #setZOrderMediaOverlay}.
+     *
+     * @param onTop {@code true} to be on top of its window, {@code false} otherwise.
+     */
+    @FlaggedApi(Flags.FLAG_TIAF_V_APIS)
+    public void setZOrderOnTop(boolean onTop) {
+        if (mSurfaceView != null) {
+            mSurfaceView.setZOrderMediaOverlay(false);
+            mSurfaceView.setZOrderOnTop(onTop);
+        }
+    }
+
     private void resetSurfaceView() {
         if (mSurfaceView != null) {
             mSurfaceView.getHolder().removeCallback(mSurfaceHolderCallback);
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index a046057..2d1fbf9 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -361,6 +361,8 @@
     APerformanceHint_setThreads; # introduced=UpsideDownCake
     APerformanceHint_setPreferPowerEfficiency; # introduced=VanillaIceCream
     APerformanceHint_reportActualWorkDuration2; # introduced=VanillaIceCream
+    APerformanceHint_notifyWorkloadIncrease; # introduced=36
+    APerformanceHint_notifyWorkloadReset; # introduced=36
     AWorkDuration_create; # introduced=VanillaIceCream
     AWorkDuration_release; # introduced=VanillaIceCream
     AWorkDuration_setWorkPeriodStartTimestampNanos; # introduced=VanillaIceCream
@@ -379,6 +381,8 @@
     APerformanceHint_getThreadIds;
     APerformanceHint_createSessionInternal;
     APerformanceHint_setUseFMQForTesting;
+    APerformanceHint_getRateLimiterPropertiesForTesting;
+    APerformanceHint_setUseNewLoadHintBehaviorForTesting;
     extern "C++" {
         ASurfaceControl_registerSurfaceStatsListener*;
         ASurfaceControl_unregisterSurfaceStatsListener*;
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index 15f77ce..e2fa94d 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -33,12 +33,14 @@
 #include <android/performance_hint.h>
 #include <android/trace.h>
 #include <android_os.h>
+#include <cutils/trace.h>
 #include <fmq/AidlMessageQueue.h>
 #include <inttypes.h>
 #include <performance_hint_private.h>
 #include <utils/SystemClock.h>
 
 #include <chrono>
+#include <format>
 #include <future>
 #include <set>
 #include <utility>
@@ -63,6 +65,22 @@
 constexpr int64_t SEND_HINT_TIMEOUT = std::chrono::nanoseconds(100ms).count();
 struct AWorkDuration : public hal::WorkDuration {};
 
+// A pair of values that determine the behavior of the
+// load hint rate limiter, to only allow "X hints every Y seconds"
+constexpr double kLoadHintInterval = std::chrono::nanoseconds(2s).count();
+constexpr double kMaxLoadHintsPerInterval = 20;
+constexpr double kReplenishRate = kMaxLoadHintsPerInterval / kLoadHintInterval;
+bool kForceNewHintBehavior = false;
+
+template <class T>
+constexpr int32_t enum_size() {
+    return static_cast<int32_t>(*(ndk::enum_range<T>().end() - 1)) + 1;
+}
+
+bool useNewLoadHintBehavior() {
+    return android::os::adpf_use_load_hints() || kForceNewHintBehavior;
+}
+
 // Shared lock for the whole PerformanceHintManager and sessions
 static std::mutex sHintMutex = std::mutex{};
 class FMQWrapper {
@@ -76,7 +94,8 @@
                                    hal::WorkDuration* durations, size_t count) REQUIRES(sHintMutex);
     bool updateTargetWorkDuration(std::optional<hal::SessionConfig>& config,
                                   int64_t targetDurationNanos) REQUIRES(sHintMutex);
-    bool sendHint(std::optional<hal::SessionConfig>& config, SessionHint hint) REQUIRES(sHintMutex);
+    bool sendHints(std::optional<hal::SessionConfig>& config, std::vector<hal::SessionHint>& hint,
+                   int64_t now) REQUIRES(sHintMutex);
     bool setMode(std::optional<hal::SessionConfig>& config, hal::SessionMode, bool enabled)
             REQUIRES(sHintMutex);
     void setToken(ndk::SpAIBinder& token);
@@ -86,10 +105,11 @@
 private:
     template <HalChannelMessageContents::Tag T, bool urgent = false,
               class C = HalChannelMessageContents::_at<T>>
-    bool sendMessages(std::optional<hal::SessionConfig>& config, C* message, size_t count = 1)
-            REQUIRES(sHintMutex);
+    bool sendMessages(std::optional<hal::SessionConfig>& config, C* message, size_t count = 1,
+                      int64_t now = ::android::uptimeNanos()) REQUIRES(sHintMutex);
     template <HalChannelMessageContents::Tag T, class C = HalChannelMessageContents::_at<T>>
-    void writeBuffer(C* message, hal::SessionConfig& config, size_t count) REQUIRES(sHintMutex);
+    void writeBuffer(C* message, hal::SessionConfig& config, size_t count, int64_t now)
+            REQUIRES(sHintMutex);
 
     bool isActiveLocked() REQUIRES(sHintMutex);
     bool updatePersistentTransaction() REQUIRES(sHintMutex);
@@ -120,6 +140,7 @@
                                            hal::SessionTag tag = hal::SessionTag::APP);
     int64_t getPreferredRateNanos() const;
     FMQWrapper& getFMQWrapper();
+    bool canSendLoadHints(std::vector<hal::SessionHint>& hints, int64_t now) REQUIRES(sHintMutex);
 
 private:
     // Necessary to create an empty binder object
@@ -138,6 +159,8 @@
     ndk::SpAIBinder mToken;
     const int64_t mPreferredRateNanos;
     FMQWrapper mFMQWrapper;
+    double mHintBudget = kMaxLoadHintsPerInterval;
+    int64_t mLastBudgetReplenish = 0;
 };
 
 struct APerformanceHintSession {
@@ -151,7 +174,9 @@
 
     int updateTargetWorkDuration(int64_t targetDurationNanos);
     int reportActualWorkDuration(int64_t actualDurationNanos);
-    int sendHint(SessionHint hint);
+    int sendHints(std::vector<hal::SessionHint>& hints, int64_t now, const char* debugName);
+    int notifyWorkloadIncrease(bool cpu, bool gpu, const char* debugName);
+    int notifyWorkloadReset(bool cpu, bool gpu, const char* debugName);
     int setThreads(const int32_t* threadIds, size_t size);
     int getThreadIds(int32_t* const threadIds, size_t* size);
     int setPreferPowerEfficiency(bool enabled);
@@ -173,6 +198,8 @@
     // Last target hit timestamp
     int64_t mLastTargetMetTimestamp GUARDED_BY(sHintMutex);
     // Last hint reported from sendHint indexed by hint value
+    // This is only used by the old rate limiter impl and is replaced
+    // with the new rate limiter under a flag
     std::vector<int64_t> mLastHintSentTimestamp GUARDED_BY(sHintMutex);
     // Cached samples
     std::vector<hal::WorkDuration> mActualWorkDurations GUARDED_BY(sHintMutex);
@@ -255,6 +282,21 @@
     return new APerformanceHintManager(manager, preferredRateNanos);
 }
 
+bool APerformanceHintManager::canSendLoadHints(std::vector<hal::SessionHint>& hints, int64_t now) {
+    mHintBudget =
+            std::max(kMaxLoadHintsPerInterval,
+                     mHintBudget +
+                             static_cast<double>(now - mLastBudgetReplenish) * kReplenishRate);
+    mLastBudgetReplenish = now;
+
+    // If this youngest timestamp isn't older than the timeout time, we can't send
+    if (hints.size() > mHintBudget) {
+        return false;
+    }
+    mHintBudget -= hints.size();
+    return true;
+}
+
 APerformanceHintSession* APerformanceHintManager::createSession(
         const int32_t* threadIds, size_t size, int64_t initialTargetWorkDurationNanos,
         hal::SessionTag tag) {
@@ -292,9 +334,7 @@
 
 // ===================================== APerformanceHintSession implementation
 
-constexpr int kNumEnums =
-        ndk::enum_range<hal::SessionHint>().end() - ndk::enum_range<hal::SessionHint>().begin();
-
+constexpr int kNumEnums = enum_size<hal::SessionHint>();
 APerformanceHintSession::APerformanceHintSession(std::shared_ptr<IHintManager> hintManager,
                                                  std::shared_ptr<IHintSession> session,
                                                  int64_t preferredRateNanos,
@@ -361,31 +401,83 @@
     return reportActualWorkDurationInternal(static_cast<AWorkDuration*>(&workDuration));
 }
 
-int APerformanceHintSession::sendHint(SessionHint hint) {
+int APerformanceHintSession::sendHints(std::vector<hal::SessionHint>& hints, int64_t now,
+                                       const char*) {
     std::scoped_lock lock(sHintMutex);
-    if (hint < 0 || hint >= static_cast<int32_t>(mLastHintSentTimestamp.size())) {
-        ALOGE("%s: invalid session hint %d", __FUNCTION__, hint);
+    if (hints.empty()) {
         return EINVAL;
     }
-    int64_t now = uptimeNanos();
-
-    // Limit sendHint to a pre-detemined rate for safety
-    if (now < (mLastHintSentTimestamp[hint] + SEND_HINT_TIMEOUT)) {
-        return 0;
-    }
-
-    if (!getFMQ().sendHint(mSessionConfig, hint)) {
-        ndk::ScopedAStatus ret = mHintSession->sendHint(hint);
-
-        if (!ret.isOk()) {
-            ALOGE("%s: HintSession sendHint failed: %s", __FUNCTION__, ret.getMessage());
-            return EPIPE;
+    for (auto&& hint : hints) {
+        if (static_cast<int32_t>(hint) < 0 || static_cast<int32_t>(hint) >= kNumEnums) {
+            ALOGE("%s: invalid session hint %d", __FUNCTION__, hint);
+            return EINVAL;
         }
     }
-    mLastHintSentTimestamp[hint] = now;
+
+    if (useNewLoadHintBehavior()) {
+        if (!APerformanceHintManager::getInstance()->canSendLoadHints(hints, now)) {
+            return EBUSY;
+        }
+    }
+    // keep old rate limiter behavior for legacy flag
+    else {
+        for (auto&& hint : hints) {
+            if (now < (mLastHintSentTimestamp[static_cast<int32_t>(hint)] + SEND_HINT_TIMEOUT)) {
+                return EBUSY;
+            }
+        }
+    }
+
+    if (!getFMQ().sendHints(mSessionConfig, hints, now)) {
+        for (auto&& hint : hints) {
+            ndk::ScopedAStatus ret = mHintSession->sendHint(static_cast<int32_t>(hint));
+
+            if (!ret.isOk()) {
+                ALOGE("%s: HintSession sendHint failed: %s", __FUNCTION__, ret.getMessage());
+                return EPIPE;
+            }
+        }
+    }
+
+    if (!useNewLoadHintBehavior()) {
+        for (auto&& hint : hints) {
+            mLastHintSentTimestamp[static_cast<int32_t>(hint)] = now;
+        }
+    }
+
+    if (ATrace_isEnabled()) {
+        ATRACE_INSTANT("Sending load hint");
+    }
+
     return 0;
 }
 
+int APerformanceHintSession::notifyWorkloadIncrease(bool cpu, bool gpu, const char* debugName) {
+    std::vector<hal::SessionHint> hints(2);
+    hints.clear();
+    if (cpu) {
+        hints.push_back(hal::SessionHint::CPU_LOAD_UP);
+    }
+    if (gpu) {
+        hints.push_back(hal::SessionHint::GPU_LOAD_UP);
+    }
+    int64_t now = ::android::uptimeNanos();
+    return sendHints(hints, now, debugName);
+}
+
+int APerformanceHintSession::notifyWorkloadReset(bool cpu, bool gpu, const char* debugName) {
+    std::vector<hal::SessionHint> hints(2);
+    hints.clear();
+    if (cpu) {
+        hints.push_back(hal::SessionHint::CPU_LOAD_RESET);
+    }
+    if (gpu) {
+        hints.push_back(hal::SessionHint::GPU_LOAD_RESET);
+    }
+    int64_t now = ::android::uptimeNanos();
+    return sendHints(hints, now, debugName);
+}
+
 int APerformanceHintSession::setThreads(const int32_t* threadIds, size_t size) {
     if (size == 0) {
         ALOGE("%s: the list of thread ids must not be empty.", __FUNCTION__);
@@ -565,24 +657,25 @@
 }
 
 template <HalChannelMessageContents::Tag T, class C>
-void FMQWrapper::writeBuffer(C* message, hal::SessionConfig& config, size_t) {
-    new (mFmqTransaction.getSlot(0)) hal::ChannelMessage{
-            .sessionID = static_cast<int32_t>(config.id),
-            .timeStampNanos = ::android::uptimeNanos(),
-            .data = HalChannelMessageContents::make<T, C>(std::move(*message)),
-    };
+void FMQWrapper::writeBuffer(C* message, hal::SessionConfig& config, size_t count, int64_t now) {
+    for (size_t i = 0; i < count; ++i) {
+        new (mFmqTransaction.getSlot(i)) hal::ChannelMessage{
+                .sessionID = static_cast<int32_t>(config.id),
+                .timeStampNanos = now,
+                .data = HalChannelMessageContents::make<T, C>(std::move(*(message + i))),
+        };
+    }
 }
 
 template <>
 void FMQWrapper::writeBuffer<HalChannelMessageContents::workDuration>(hal::WorkDuration* messages,
                                                                       hal::SessionConfig& config,
-                                                                      size_t count) {
+                                                                      size_t count, int64_t now) {
     for (size_t i = 0; i < count; ++i) {
         hal::WorkDuration& message = messages[i];
         new (mFmqTransaction.getSlot(i)) hal::ChannelMessage{
                 .sessionID = static_cast<int32_t>(config.id),
-                .timeStampNanos =
-                        (i == count - 1) ? ::android::uptimeNanos() : message.timeStampNanos,
+                .timeStampNanos = (i == count - 1) ? now : message.timeStampNanos,
                 .data = HalChannelMessageContents::make<HalChannelMessageContents::workDuration,
                                                         hal::WorkDurationFixedV1>({
                         .durationNanos = message.cpuDurationNanos,
@@ -595,7 +688,8 @@
 }
 
 template <HalChannelMessageContents::Tag T, bool urgent, class C>
-bool FMQWrapper::sendMessages(std::optional<hal::SessionConfig>& config, C* message, size_t count) {
+bool FMQWrapper::sendMessages(std::optional<hal::SessionConfig>& config, C* message, size_t count,
+                              int64_t now) {
     if (!isActiveLocked() || !config.has_value() || mCorrupted) {
         return false;
     }
@@ -609,7 +703,7 @@
             return false;
         }
     }
-    writeBuffer<T, C>(message, *config, count);
+    writeBuffer<T, C>(message, *config, count, now);
     mQueue->commitWrite(count);
     mEventFlag->wake(mWriteMask);
     // Re-create the persistent transaction after writing
@@ -641,10 +735,9 @@
     return sendMessages<HalChannelMessageContents::targetDuration>(config, &targetDurationNanos);
 }
 
-bool FMQWrapper::sendHint(std::optional<hal::SessionConfig>& config, SessionHint hint) {
-    return sendMessages<HalChannelMessageContents::hint>(config,
-                                                         reinterpret_cast<hal::SessionHint*>(
-                                                                 &hint));
+bool FMQWrapper::sendHints(std::optional<hal::SessionConfig>& config,
+                           std::vector<hal::SessionHint>& hints, int64_t now) {
+    return sendMessages<HalChannelMessageContents::hint>(config, hints.data(), hints.size(), now);
 }
 
 bool FMQWrapper::setMode(std::optional<hal::SessionConfig>& config, hal::SessionMode mode,
@@ -758,7 +851,9 @@
 
 int APerformanceHint_sendHint(APerformanceHintSession* session, SessionHint hint) {
     VALIDATE_PTR(session)
-    return session->sendHint(hint);
+    std::vector<hal::SessionHint> hints{static_cast<hal::SessionHint>(hint)};
+    int64_t now = ::android::uptimeNanos();
+    return session->sendHints(hints, now, "HWUI hint");
 }
 
 int APerformanceHint_setThreads(APerformanceHintSession* session, const pid_t* threadIds,
@@ -791,6 +886,26 @@
     return session->reportActualWorkDuration(workDurationPtr);
 }
 
+int APerformanceHint_notifyWorkloadIncrease(APerformanceHintSession* session, bool cpu, bool gpu,
+                                            const char* debugName) {
+    VALIDATE_PTR(session)
+    VALIDATE_PTR(debugName)
+    if (!useNewLoadHintBehavior()) {
+        return ENOTSUP;
+    }
+    return session->notifyWorkloadIncrease(cpu, gpu, debugName);
+}
+
+int APerformanceHint_notifyWorkloadReset(APerformanceHintSession* session, bool cpu, bool gpu,
+                                         const char* debugName) {
+    VALIDATE_PTR(session)
+    VALIDATE_PTR(debugName)
+    if (!useNewLoadHintBehavior()) {
+        return ENOTSUP;
+    }
+    return session->notifyWorkloadReset(cpu, gpu, debugName);
+}
+
 AWorkDuration* AWorkDuration_create() {
     return new AWorkDuration();
 }
@@ -838,3 +953,13 @@
 void APerformanceHint_setUseFMQForTesting(bool enabled) {
     gForceFMQEnabled = enabled;
 }
+
+void APerformanceHint_getRateLimiterPropertiesForTesting(int32_t* maxLoadHintsPerInterval,
+                                                         int64_t* loadHintInterval) {
+    *maxLoadHintsPerInterval = kMaxLoadHintsPerInterval;
+    *loadHintInterval = kLoadHintInterval;
+}
+
+void APerformanceHint_setUseNewLoadHintBehaviorForTesting(bool newBehavior) {
+    kForceNewHintBehavior = newBehavior;
+}
diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
index 9de3a6f..f707a0e 100644
--- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
+++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
@@ -66,6 +66,18 @@
                  std::optional<hal::ChannelConfig>* _aidl_return),
                 (override));
     MOCK_METHOD(ScopedAStatus, closeSessionChannel, (), (override));
+    MOCK_METHOD(ScopedAStatus, getCpuHeadroom,
+                (const ::aidl::android::os::CpuHeadroomParamsInternal& in_params,
+                 std::vector<float>* _aidl_return),
+                (override));
+    MOCK_METHOD(ScopedAStatus, getCpuHeadroomMinIntervalMillis, (int64_t* _aidl_return),
+                (override));
+    MOCK_METHOD(ScopedAStatus, getGpuHeadroom,
+                (const ::aidl::android::os::GpuHeadroomParamsInternal& in_params,
+                 float* _aidl_return),
+                (override));
+    MOCK_METHOD(ScopedAStatus, getGpuHeadroomMinIntervalMillis, (int64_t* _aidl_return),
+                (override));
     MOCK_METHOD(SpAIBinder, asBinder, (), (override));
     MOCK_METHOD(bool, isRemote, (), (override));
 };
@@ -90,7 +102,10 @@
 public:
     void SetUp() override {
         mMockIHintManager = ndk::SharedRefBase::make<NiceMock<MockIHintManager>>();
+        APerformanceHint_getRateLimiterPropertiesForTesting(&mMaxLoadHintsPerInterval,
+                                                            &mLoadHintInterval);
         APerformanceHint_setIHintManagerForTesting(&mMockIHintManager);
+        APerformanceHint_setUseNewLoadHintBehaviorForTesting(true);
     }
 
     void TearDown() override {
@@ -176,6 +191,9 @@
     int kMockQueueSize = 20;
     bool mUsingFMQ = false;
 
+    int32_t mMaxLoadHintsPerInterval;
+    int64_t mLoadHintInterval;
+
     template <HalChannelMessageContents::Tag T, class C = HalChannelMessageContents::_at<T>>
     void expectToReadFromFmq(C expected) {
         hal::ChannelMessage readData;
@@ -218,7 +236,6 @@
     EXPECT_CALL(*mMockSession, reportActualWorkDuration2(_)).Times(Exactly(1));
     result = APerformanceHint_reportActualWorkDuration(session, actualDurationNanos);
     EXPECT_EQ(0, result);
-
     result = APerformanceHint_updateTargetWorkDuration(session, -1L);
     EXPECT_EQ(EINVAL, result);
     result = APerformanceHint_reportActualWorkDuration(session, -1L);
@@ -228,18 +245,28 @@
     EXPECT_CALL(*mMockSession, sendHint(Eq(hintId))).Times(Exactly(1));
     result = APerformanceHint_sendHint(session, hintId);
     EXPECT_EQ(0, result);
-    usleep(110000); // Sleep for longer than the update timeout.
-    EXPECT_CALL(*mMockSession, sendHint(Eq(hintId))).Times(Exactly(1));
-    result = APerformanceHint_sendHint(session, hintId);
+    EXPECT_CALL(*mMockSession, sendHint(Eq(SessionHint::CPU_LOAD_UP))).Times(Exactly(1));
+    result = APerformanceHint_notifyWorkloadIncrease(session, true, false, "Test hint");
     EXPECT_EQ(0, result);
-    // Expect to get rate limited if we try to send faster than the limiter allows
-    EXPECT_CALL(*mMockSession, sendHint(Eq(hintId))).Times(Exactly(0));
-    result = APerformanceHint_sendHint(session, hintId);
+    EXPECT_CALL(*mMockSession, sendHint(Eq(SessionHint::CPU_LOAD_RESET))).Times(Exactly(1));
+    EXPECT_CALL(*mMockSession, sendHint(Eq(SessionHint::GPU_LOAD_RESET))).Times(Exactly(1));
+    result = APerformanceHint_notifyWorkloadReset(session, true, true, "Test hint");
     EXPECT_EQ(0, result);
 
     result = APerformanceHint_sendHint(session, static_cast<SessionHint>(-1));
     EXPECT_EQ(EINVAL, result);
 
+    Mock::VerifyAndClearExpectations(mMockSession.get());
+    for (int i = 0; i < mMaxLoadHintsPerInterval; ++i) {
+        APerformanceHint_sendHint(session, hintId);
+    }
+
+    // Expect to get rate limited if we try to send faster than the limiter allows
+    EXPECT_CALL(*mMockSession, sendHint(_)).Times(Exactly(0));
+    result = APerformanceHint_notifyWorkloadIncrease(session, true, true, "Test hint");
+    EXPECT_EQ(result, EBUSY);
+    EXPECT_CALL(*mMockSession, sendHint(_)).Times(Exactly(0));
+    result = APerformanceHint_notifyWorkloadReset(session, true, true, "Test hint");
     EXPECT_CALL(*mMockSession, close()).Times(Exactly(1));
     APerformanceHint_closeSession(session);
 }
diff --git a/packages/Shell/OWNERS b/packages/Shell/OWNERS
index 8feefa5..d4b5b862 100644
--- a/packages/Shell/OWNERS
+++ b/packages/Shell/OWNERS
@@ -12,3 +12,4 @@
 cbrubaker@google.com
 omakoto@google.com
 michaelwr@google.com
+ronish@google.com
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 7c478ac..7f25b51 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -153,6 +153,10 @@
     static final String INTENT_BUGREPORT_FINISHED =
             "com.android.internal.intent.action.BUGREPORT_FINISHED";
 
+    // Intent sent to notify external apps that bugreport aborted due to error.
+    static final String INTENT_BUGREPORT_ABORTED =
+            "com.android.internal.intent.action.BUGREPORT_ABORTED";
+
     // Internal intents used on notification actions.
     static final String INTENT_BUGREPORT_CANCEL = "android.intent.action.BUGREPORT_CANCEL";
     static final String INTENT_BUGREPORT_SHARE = "android.intent.action.BUGREPORT_SHARE";
@@ -174,6 +178,8 @@
     static final String EXTRA_INFO = "android.intent.extra.INFO";
     static final String EXTRA_EXTRA_ATTACHMENT_URIS =
             "android.intent.extra.EXTRA_ATTACHMENT_URIS";
+    static final String EXTRA_ABORTED_ERROR_CODE =
+            "android.intent.extra.EXTRA_ABORTED_ERROR_CODE";
 
     private static final ThreadFactory sBugreportManagerCallbackThreadFactory =
             new ThreadFactory() {
@@ -404,6 +410,7 @@
         @Override
         public void onError(@BugreportErrorCode int errorCode) {
             synchronized (mLock) {
+                sendBugreportAbortedBroadcastLocked(errorCode);
                 stopProgressLocked(mInfo.id);
                 mInfo.deleteEmptyFiles();
             }
@@ -460,6 +467,13 @@
                 onBugreportFinished(mInfo);
             }
         }
+
+        @GuardedBy("mLock")
+        private void sendBugreportAbortedBroadcastLocked(@BugreportErrorCode int errorCode) {
+            final Intent intent = new Intent(INTENT_BUGREPORT_ABORTED);
+            intent.putExtra(EXTRA_ABORTED_ERROR_CODE, errorCode);
+            mContext.sendBroadcast(intent, android.Manifest.permission.DUMP);
+        }
     }
 
     private void sendRemoteBugreportFinishedBroadcast(Context context,
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 0948931..dafe38d 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -90,8 +90,6 @@
         "tests/src/**/systemui/globalactions/GlobalActionsDialogLiteTest.java",
         "tests/src/**/systemui/globalactions/GlobalActionsImeTest.java",
         "tests/src/**/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt",
-        "tests/src/**/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt",
-        "tests/src/**/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt",
         "tests/src/**/systemui/media/dialog/MediaOutputAdapterTest.java",
         "tests/src/**/systemui/media/dialog/MediaOutputBaseDialogTest.java",
         "tests/src/**/systemui/media/dialog/MediaOutputBroadcastDialogTest.java",
@@ -134,7 +132,6 @@
         "tests/src/**/systemui/accessibility/floatingmenu/MenuViewLayerTest.java",
         "tests/src/**/systemui/accessibility/floatingmenu/MenuViewTest.java",
         "tests/src/**/systemui/classifier/PointerCountClassifierTest.java",
-        "tests/src/**/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt",
         "tests/src/**/systemui/accessibility/floatingmenu/RadiiAnimatorTest.java",
         "tests/src/**/systemui/screenrecord/RecordingControllerTest.java",
         "tests/src/**/systemui/screenshot/RequestProcessorTest.kt",
@@ -216,8 +213,6 @@
         "tests/src/**/systemui/statusbar/KeyboardShortcutsTest.java",
         "tests/src/**/systemui/statusbar/KeyguardIndicationControllerWithCoroutinesTest.kt",
         "tests/src/**/systemui/statusbar/notification/AssistantFeedbackControllerTest.java",
-        "tests/src/**/systemui/statusbar/notification/PropertyAnimatorTest.java",
-        "tests/src/**/systemui/statusbar/notification/collection/NotifCollectionTest.java",
         "tests/src/**/systemui/statusbar/notification/collection/NotificationEntryTest.java",
         "tests/src/**/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt",
         "tests/src/**/systemui/statusbar/notification/collection/ShadeListBuilderTest.java",
@@ -233,9 +228,7 @@
         "tests/src/**/systemui/statusbar/notification/row/NotificationConversationInfoTest.java",
         "tests/src/**/systemui/statusbar/notification/row/NotificationGutsManagerTest.java",
         "tests/src/**/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt",
-        "tests/src/**/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt",
         "tests/src/**/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt",
-        "tests/src/**/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java",
         "tests/src/**/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java",
         "tests/src/**/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt",
         "tests/src/**/systemui/statusbar/phone/AutoTileManagerTest.java",
@@ -250,7 +243,6 @@
         "tests/src/**/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelImplTest.kt",
         "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt",
         "tests/src/**/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt",
-        "tests/src/**/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt",
         "tests/src/**/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt",
         "tests/src/**/systemui/statusbar/policy/CallbackControllerTest.java",
         "tests/src/**/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java",
@@ -263,11 +255,9 @@
         "tests/src/**/systemui/theme/ThemeOverlayApplierTest.java",
         "tests/src/**/systemui/touch/TouchInsetManagerTest.java",
         "tests/src/**/systemui/util/LifecycleFragmentTest.java",
-        "tests/src/**/systemui/util/TestableAlertDialogTest.kt",
         "tests/src/**/systemui/util/kotlin/PairwiseFlowTest",
         "tests/src/**/systemui/util/sensors/AsyncManagerTest.java",
         "tests/src/**/systemui/util/sensors/ThresholdSensorImplTest.java",
-        "tests/src/**/systemui/util/wakelock/KeepAwakeAnimationListenerTest.java",
         "tests/src/**/systemui/volume/VolumeDialogImplTest.java",
         "tests/src/**/systemui/wallet/controller/QuickAccessWalletControllerTest.java",
         "tests/src/**/systemui/wallet/ui/WalletScreenControllerTest.java",
@@ -288,9 +278,6 @@
         "tests/src/**/systemui/qs/QSImplTest.java",
         "tests/src/**/systemui/qs/panels/ui/compose/DragAndDropTest.kt",
         "tests/src/**/systemui/qs/panels/ui/compose/ResizingTest.kt",
-        "tests/src/**/systemui/screenshot/ActionExecutorTest.kt",
-        "tests/src/**/systemui/screenshot/ScreenshotDetectionControllerTest.kt",
-        "tests/src/**/systemui/screenshot/TakeScreenshotServiceTest.kt",
         "tests/src/**/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java",
         "tests/src/**/systemui/accessibility/floatingmenu/PositionTest.java",
         "tests/src/**/systemui/animation/TransitionAnimatorTest.kt",
@@ -303,10 +290,45 @@
         "tests/src/**/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt",
         "tests/src/**/systemui/statusbar/policy/RotationLockControllerImplTest.java",
         "tests/src/**/systemui/statusbar/phone/ScrimControllerTest.java",
-        "tests/src/**/systemui/stylus/StylusUsiPowerStartableTest.kt",
         "tests/src/**/systemui/toast/ToastUITest.java",
         "tests/src/**/systemui/statusbar/policy/FlashlightControllerImplTest.kt",
         "tests/src/**/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt",
+        "tests/src/**/systemui/stylus/StylusUsiPowerUiTest.kt",
+    ],
+}
+
+// Files which use ExtendedMockito on the device.
+filegroup {
+    name: "SystemUI-tests-broken-robofiles-mockito-extended",
+    srcs: [
+        "tests/src/**/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt",
+        "tests/src/**/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt",
+        "tests/src/**/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt",
+        "tests/src/**/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt",
+        "tests/src/**/systemui/bluetooth/qsdialog/AudioSharingButtonViewModelTest.kt",
+        "tests/src/**/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt",
+        "tests/src/**/systemui/stylus/StylusManagerTest.kt",
+        "tests/src/**/systemui/recents/OverviewProxyServiceTest.kt",
+        "tests/src/**/systemui/DisplayCutoutBaseViewTest.kt",
+        "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt",
+        "tests/src/**/systemui/statusbar/policy/BatteryControllerTest.java",
+        "tests/src/**/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt",
+        "tests/src/**/systemui/statusbar/KeyboardShortcutsReceiverTest.java",
+        "tests/src/**/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt",
+        "tests/src/**/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt",
+        "tests/src/**/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandlerTest.kt",
+        "tests/src/**/systemui/qs/tiles/HotspotTileTest.java",
+        "tests/src/**/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java",
+        "tests/src/**/systemui/navigationbar/NavigationBarControllerImplTest.java",
+        "tests/src/**/systemui/wmshell/BubblesTest.java",
+        "tests/src/**/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java",
+        "tests/src/**/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java",
+        "tests/src/**/systemui/shared/system/RemoteTransitionTest.java",
+        "tests/src/**/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java",
+        "tests/src/**/systemui/qs/external/TileLifecycleManagerTest.java",
+        "tests/src/**/systemui/ScreenDecorationsTest.java",
+        "tests/src/**/keyguard/CarrierTextManagerTest.java",
+        "tests/src/**/keyguard/KeyguardUpdateMonitorTest.java",
     ],
 }
 
@@ -314,29 +336,23 @@
 filegroup {
     name: "SystemUI-tests-broken-robofiles-compile",
     srcs: [
-        "tests/src/**/keyguard/KeyguardUpdateMonitorTest.java",
         "tests/src/**/systemui/statusbar/notification/icon/IconManagerTest.kt",
         "tests/src/**/systemui/statusbar/KeyguardIndicationControllerTest.java",
         "tests/src/**/systemui/doze/DozeScreenStateTest.java",
-        "tests/src/**/keyguard/CarrierTextManagerTest.java",
         "tests/src/**/systemui/notetask/NoteTaskInitializerTest.kt",
         "tests/src/**/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt",
         "tests/src/**/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt",
         "tests/src/**/systemui/controls/management/ControlsFavoritingActivityTest.kt",
         "tests/src/**/systemui/controls/management/ControlsProviderSelectorActivityTest.kt",
         "tests/src/**/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt",
-        "tests/src/**/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt",
         "tests/src/**/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt",
         "tests/src/**/systemui/qs/tileimpl/QSTileViewImplTest.kt",
-        "tests/src/**/systemui/statusbar/policy/BatteryStateNotifierTest.kt",
         "tests/src/**/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt",
         "tests/src/**/keyguard/ClockEventControllerTest.kt",
         "tests/src/**/systemui/bluetooth/qsdialog/BluetoothAutoOnRepositoryTest.kt",
-        "tests/src/**/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt",
         "tests/src/**/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt",
         "tests/src/**/systemui/bluetooth/qsdialog/BluetoothTileDialogRepositoryTest.kt",
         "tests/src/**/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt",
-        "tests/src/**/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt",
         "tests/src/**/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt",
         "tests/src/**/systemui/broadcast/UserBroadcastDispatcherTest.kt",
         "tests/src/**/systemui/charging/WiredChargingRippleControllerTest.kt",
@@ -351,7 +367,6 @@
         "tests/src/**/systemui/controls/ui/SelectionItemTest.kt",
         "tests/src/**/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt",
         "tests/src/**/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt",
-        "tests/src/**/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt",
         "tests/src/**/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt",
         "tests/src/**/systemui/media/controls/ui/animation/AnimationBindHandlerTest.kt",
         "tests/src/**/systemui/media/controls/ui/animation/ColorSchemeTransitionTest.kt",
@@ -417,40 +432,9 @@
         "tests/src/**/systemui/statusbar/policy/VariableDateViewControllerTest.kt",
         "tests/src/**/systemui/statusbar/policy/WalletControllerImplTest.kt",
         "tests/src/**/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt",
-        "tests/src/**/systemui/stylus/StylusUsiPowerUiTest.kt",
         "tests/src/**/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt",
-        "tests/src/**/systemui/ScreenDecorationsTest.java",
-        "tests/src/**/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandlerTest.kt",
-        "tests/src/**/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt",
-        "tests/src/**/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt",
-        "tests/src/**/systemui/shared/system/RemoteTransitionTest.java",
-        "tests/src/**/systemui/navigationbar/NavigationBarControllerImplTest.java",
-        "tests/src/**/systemui/bluetooth/qsdialog/AudioSharingButtonViewModelTest.kt",
-        "tests/src/**/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt",
-        "tests/src/**/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt",
-        "tests/src/**/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt",
-        "tests/src/**/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt",
-        "tests/src/**/systemui/DisplayCutoutBaseViewTest.kt",
-        "tests/src/**/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java",
-        "tests/src/**/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java",
-        "tests/src/**/systemui/qs/tiles/HotspotTileTest.java",
-        "tests/src/**/systemui/qs/external/TileLifecycleManagerTest.java",
-        "tests/src/**/systemui/recents/OverviewProxyServiceTest.kt",
-        "tests/src/**/systemui/stylus/StylusManagerTest.kt",
-        "tests/src/**/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java",
-        "tests/src/**/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java",
         "tests/src/**/systemui/statusbar/policy/BatteryControllerStartableTest.java",
-        "tests/src/**/systemui/statusbar/policy/BatteryControllerTest.java",
-        "tests/src/**/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt",
-        "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt",
-        "tests/src/**/systemui/statusbar/KeyboardShortcutsReceiverTest.java",
-        "tests/src/**/systemui/wmshell/BubblesTest.java",
-        "tests/src/**/systemui/power/PowerUITest.java",
-        "tests/src/**/systemui/qs/QSSecurityFooterTest.java",
-        "tests/src/**/systemui/qs/tileimpl/QSTileImplTest.java",
         "tests/src/**/systemui/shared/plugins/PluginActionManagerTest.java",
-        "tests/src/**/systemui/statusbar/CommandQueueTest.java",
-        "tests/src/**/systemui/statusbar/connectivity/CallbackHandlerTest.java",
         "tests/src/**/systemui/statusbar/policy/SecurityControllerTest.java",
         "tests/src/**/systemui/shared/clocks/view/SimpleDigitalClockTextViewTest.kt",
     ],
@@ -916,6 +900,7 @@
         ":SystemUI-tests-robofiles",
     ],
     exclude_srcs: [
+        ":SystemUI-tests-broken-robofiles-mockito-extended",
         ":SystemUI-tests-broken-robofiles-compile",
         ":SystemUI-tests-broken-robofiles-run",
         ":SystemUI-tests-broken-robofiles-sysui-run",
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 380344a..e47704eb 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -153,5 +153,11 @@
     {
       "path": "cts/tests/tests/multiuser"
     }
+  ],
+
+  "sysui-e2e-presubmit": [
+    {
+      "name": "PlatformScenarioTests_SysUI_Presubmit"
+    }
   ]
 }
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 20c8c6a3..2d32fd7 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
@@ -36,6 +36,7 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.CornerSize
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material3.Icon
 import androidx.compose.material3.LocalContentColor
@@ -84,6 +85,7 @@
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 import com.android.systemui.qs.ui.composable.QuickSettings
 import com.android.systemui.qs.ui.composable.QuickSettingsTheme
+import com.android.systemui.qs.ui.compose.borderOnFocus
 import com.android.systemui.res.R
 import kotlinx.coroutines.launch
 
@@ -264,7 +266,11 @@
         color = colorAttr(model.backgroundColor),
         shape = CircleShape,
         onClick = model.onClick,
-        modifier = modifier,
+        modifier =
+            modifier.borderOnFocus(
+                color = MaterialTheme.colorScheme.secondary,
+                CornerSize(percent = 50),
+            ),
     ) {
         val tint = model.iconTint?.let { Color(it) } ?: Color.Unspecified
         Icon(model.icon, tint = tint, modifier = Modifier.size(20.dp))
@@ -291,7 +297,11 @@
         shape = CircleShape,
         onClick = onClick,
         interactionSource = interactionSource,
-        modifier = modifier,
+        modifier =
+            modifier.borderOnFocus(
+                color = MaterialTheme.colorScheme.secondary,
+                CornerSize(percent = 50),
+            ),
     ) {
         Box(Modifier.size(40.dp)) {
             Box(
@@ -342,7 +352,10 @@
         color = colorAttr(R.attr.underSurface),
         contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
         borderStroke = BorderStroke(1.dp, colorAttr(R.attr.shadeInactive)),
-        modifier = modifier.padding(horizontal = 4.dp),
+        modifier =
+            modifier
+                .padding(horizontal = 4.dp)
+                .borderOnFocus(color = MaterialTheme.colorScheme.secondary, CornerSize(50)),
         onClick = onClick,
     ) {
         Row(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
index 2cde678..d546a5d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
@@ -15,7 +15,9 @@
 import com.android.systemui.scene.ui.composable.transitions.bouncerToLockscreenPreview
 import com.android.systemui.scene.ui.composable.transitions.communalToBouncerTransition
 import com.android.systemui.scene.ui.composable.transitions.communalToShadeTransition
+import com.android.systemui.scene.ui.composable.transitions.dreamToBouncerTransition
 import com.android.systemui.scene.ui.composable.transitions.dreamToGoneTransition
+import com.android.systemui.scene.ui.composable.transitions.dreamToShadeTransition
 import com.android.systemui.scene.ui.composable.transitions.goneToQuickSettingsTransition
 import com.android.systemui.scene.ui.composable.transitions.goneToShadeTransition
 import com.android.systemui.scene.ui.composable.transitions.goneToSplitShadeTransition
@@ -55,7 +57,9 @@
     // Scene transitions
 
     from(Scenes.Bouncer, to = Scenes.Gone) { bouncerToGoneTransition() }
+    from(Scenes.Dream, to = Scenes.Bouncer) { dreamToBouncerTransition() }
     from(Scenes.Dream, to = Scenes.Gone) { dreamToGoneTransition() }
+    from(Scenes.Dream, to = Scenes.Shade) { dreamToShadeTransition() }
     from(Scenes.Gone, to = Scenes.Shade) { goneToShadeTransition() }
     from(Scenes.Gone, to = Scenes.Shade, key = ToSplitShade) { goneToSplitShadeTransition() }
     from(Scenes.Gone, to = Scenes.Shade, key = SlightlyFasterShadeCollapse) {
diff --git a/core/java/android/text/ClientFlags.java b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromDreamToBouncerTransition.kt
similarity index 60%
rename from core/java/android/text/ClientFlags.java
rename to packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromDreamToBouncerTransition.kt
index ca88764..8cb89f9 100644
--- a/core/java/android/text/ClientFlags.java
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromDreamToBouncerTransition.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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.
@@ -14,16 +14,10 @@
  * limitations under the License.
  */
 
-package android.text;
+package com.android.systemui.scene.ui.composable.transitions
 
-/**
- * An aconfig feature flags that can be accessible from application process without
- * ContentProvider IPCs.
- *
- * When you add new flags, you have to add flag string to {@link TextFlags#TEXT_ACONFIGS_FLAGS}.
- *
- * TODO(nona): Remove this class.
- * @hide
- */
-public class ClientFlags {
+import com.android.compose.animation.scene.TransitionBuilder
+
+fun TransitionBuilder.dreamToBouncerTransition() {
+    toBouncerTransition()
 }
diff --git a/core/java/android/text/ClientFlags.java b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromDreamToShadeTransition.kt
similarity index 60%
copy from core/java/android/text/ClientFlags.java
copy to packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromDreamToShadeTransition.kt
index ca88764..e35aaae 100644
--- a/core/java/android/text/ClientFlags.java
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromDreamToShadeTransition.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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.
@@ -14,16 +14,10 @@
  * limitations under the License.
  */
 
-package android.text;
+package com.android.systemui.scene.ui.composable.transitions
 
-/**
- * An aconfig feature flags that can be accessible from application process without
- * ContentProvider IPCs.
- *
- * When you add new flags, you have to add flag string to {@link TextFlags#TEXT_ACONFIGS_FLAGS}.
- *
- * TODO(nona): Remove this class.
- * @hide
- */
-public class ClientFlags {
+import com.android.compose.animation.scene.TransitionBuilder
+
+fun TransitionBuilder.dreamToShadeTransition(durationScale: Double = 1.0) {
+    toShadeTransition(durationScale = durationScale)
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index c33d655..04c5271 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -530,16 +530,12 @@
 }
 
 internal class NestedScrollHandlerImpl(
-    private val layoutImpl: SceneTransitionLayoutImpl,
-    private val orientation: Orientation,
+    private val draggableHandler: DraggableHandlerImpl,
     internal var topOrLeftBehavior: NestedScrollBehavior,
     internal var bottomOrRightBehavior: NestedScrollBehavior,
     internal var isExternalOverscrollGesture: () -> Boolean,
     private val pointersInfoOwner: PointersInfoOwner,
 ) {
-    private val layoutState = layoutImpl.state
-    private val draggableHandler = layoutImpl.draggableHandler(orientation)
-
     val connection: PriorityNestedScrollConnection = nestedScrollConnection()
 
     private fun nestedScrollConnection(): PriorityNestedScrollConnection {
@@ -550,13 +546,15 @@
         var lastPointersDown: PointersInfo.PointersDown? = null
 
         fun shouldEnableSwipes(): Boolean {
-            return layoutImpl.contentForUserActions().shouldEnableSwipes(orientation)
+            return draggableHandler.layoutImpl
+                .contentForUserActions()
+                .shouldEnableSwipes(draggableHandler.orientation)
         }
 
         var isIntercepting = false
 
         return PriorityNestedScrollConnection(
-            orientation = orientation,
+            orientation = draggableHandler.orientation,
             canStartPreScroll = { offsetAvailable, offsetBeforeStart, _ ->
                 val pointersDown: PointersInfo.PointersDown? =
                     when (val info = pointersInfoOwner.pointersInfo()) {
@@ -578,8 +576,9 @@
                         draggableHandler.shouldImmediatelyIntercept(pointersDown)
                 if (!canInterceptSwipeTransition) return@PriorityNestedScrollConnection false
 
+                val layoutImpl = draggableHandler.layoutImpl
                 val threshold = layoutImpl.transitionInterceptionThreshold
-                val hasSnappedToIdle = layoutState.snapToIdleIfClose(threshold)
+                val hasSnappedToIdle = layoutImpl.state.snapToIdleIfClose(threshold)
                 if (hasSnappedToIdle) {
                     // If the current swipe transition is closed to 0f or 1f, then we want to
                     // interrupt the transition (snapping it to Idle) and scroll the list.
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
index fbd1cd5..955be60 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
@@ -16,7 +16,6 @@
 
 package com.android.compose.animation.scene
 
-import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
@@ -69,32 +68,28 @@
 }
 
 internal fun Modifier.nestedScrollToScene(
-    layoutImpl: SceneTransitionLayoutImpl,
-    orientation: Orientation,
+    draggableHandler: DraggableHandlerImpl,
     topOrLeftBehavior: NestedScrollBehavior,
     bottomOrRightBehavior: NestedScrollBehavior,
     isExternalOverscrollGesture: () -> Boolean,
 ) =
     this then
         NestedScrollToSceneElement(
-            layoutImpl = layoutImpl,
-            orientation = orientation,
+            draggableHandler = draggableHandler,
             topOrLeftBehavior = topOrLeftBehavior,
             bottomOrRightBehavior = bottomOrRightBehavior,
             isExternalOverscrollGesture = isExternalOverscrollGesture,
         )
 
 private data class NestedScrollToSceneElement(
-    private val layoutImpl: SceneTransitionLayoutImpl,
-    private val orientation: Orientation,
+    private val draggableHandler: DraggableHandlerImpl,
     private val topOrLeftBehavior: NestedScrollBehavior,
     private val bottomOrRightBehavior: NestedScrollBehavior,
     private val isExternalOverscrollGesture: () -> Boolean,
 ) : ModifierNodeElement<NestedScrollToSceneNode>() {
     override fun create() =
         NestedScrollToSceneNode(
-            layoutImpl = layoutImpl,
-            orientation = orientation,
+            draggableHandler = draggableHandler,
             topOrLeftBehavior = topOrLeftBehavior,
             bottomOrRightBehavior = bottomOrRightBehavior,
             isExternalOverscrollGesture = isExternalOverscrollGesture,
@@ -102,8 +97,7 @@
 
     override fun update(node: NestedScrollToSceneNode) {
         node.update(
-            layoutImpl = layoutImpl,
-            orientation = orientation,
+            draggableHandler = draggableHandler,
             topOrLeftBehavior = topOrLeftBehavior,
             bottomOrRightBehavior = bottomOrRightBehavior,
             isExternalOverscrollGesture = isExternalOverscrollGesture,
@@ -112,16 +106,14 @@
 
     override fun InspectorInfo.inspectableProperties() {
         name = "nestedScrollToScene"
-        properties["layoutImpl"] = layoutImpl
-        properties["orientation"] = orientation
+        properties["draggableHandler"] = draggableHandler
         properties["topOrLeftBehavior"] = topOrLeftBehavior
         properties["bottomOrRightBehavior"] = bottomOrRightBehavior
     }
 }
 
 private class NestedScrollToSceneNode(
-    private var layoutImpl: SceneTransitionLayoutImpl,
-    private var orientation: Orientation,
+    private var draggableHandler: DraggableHandlerImpl,
     private var topOrLeftBehavior: NestedScrollBehavior,
     private var bottomOrRightBehavior: NestedScrollBehavior,
     private var isExternalOverscrollGesture: () -> Boolean,
@@ -129,12 +121,8 @@
     private var scrollBehaviorOwner: ScrollBehaviorOwner? = null
 
     private fun findScrollBehaviorOwner(): ScrollBehaviorOwner? {
-        var behaviorOwner = scrollBehaviorOwner
-        if (behaviorOwner == null) {
-            behaviorOwner = findScrollBehaviorOwner(layoutImpl.draggableHandler(orientation))
-            scrollBehaviorOwner = behaviorOwner
-        }
-        return behaviorOwner
+        return scrollBehaviorOwner
+            ?: findScrollBehaviorOwner(draggableHandler).also { scrollBehaviorOwner = it }
     }
 
     private val updateScrollBehaviorsConnection =
@@ -177,14 +165,12 @@
     }
 
     fun update(
-        layoutImpl: SceneTransitionLayoutImpl,
-        orientation: Orientation,
+        draggableHandler: DraggableHandlerImpl,
         topOrLeftBehavior: NestedScrollBehavior,
         bottomOrRightBehavior: NestedScrollBehavior,
         isExternalOverscrollGesture: () -> Boolean,
     ) {
-        this.layoutImpl = layoutImpl
-        this.orientation = orientation
+        this.draggableHandler = draggableHandler
         this.topOrLeftBehavior = topOrLeftBehavior
         this.bottomOrRightBehavior = bottomOrRightBehavior
         this.isExternalOverscrollGesture = isExternalOverscrollGesture
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index e93cf8f7..b916b0b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -125,8 +125,8 @@
                 }
 
     // TODO(b/317958526): Lazily allocate scene gesture handlers the first time they are needed.
-    private val horizontalDraggableHandler: DraggableHandlerImpl
-    private val verticalDraggableHandler: DraggableHandlerImpl
+    internal val horizontalDraggableHandler: DraggableHandlerImpl
+    internal val verticalDraggableHandler: DraggableHandlerImpl
 
     internal val elementStateScope = ElementStateScopeImpl(this)
     internal val propertyTransformationScope = PropertyTransformationScopeImpl(this)
@@ -163,12 +163,6 @@
         state.checkThread()
     }
 
-    internal fun draggableHandler(orientation: Orientation): DraggableHandlerImpl =
-        when (orientation) {
-            Orientation.Vertical -> verticalDraggableHandler
-            Orientation.Horizontal -> horizontalDraggableHandler
-        }
-
     internal fun scene(key: SceneKey): Scene {
         return scenes[key] ?: error("Scene $key is not configured")
     }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index a448ee4..5ab306a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -165,8 +165,7 @@
 
     private val nestedScrollHandlerImpl =
         NestedScrollHandlerImpl(
-            layoutImpl = draggableHandler.layoutImpl,
-            orientation = draggableHandler.orientation,
+            draggableHandler = draggableHandler,
             topOrLeftBehavior = NestedScrollBehavior.Default,
             bottomOrRightBehavior = NestedScrollBehavior.Default,
             isExternalOverscrollGesture = { false },
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
index 8187e39..255a16c 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
@@ -16,7 +16,7 @@
 
 package com.android.compose.animation.scene.content
 
-import androidx.compose.foundation.gestures.Orientation
+import android.annotation.SuppressLint
 import androidx.compose.foundation.layout.Box
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Stable
@@ -72,6 +72,7 @@
     var targetSize by mutableStateOf(IntSize.Zero)
     var userActions by mutableStateOf(actions)
 
+    @SuppressLint("NotConstructor")
     @Composable
     fun Content(modifier: Modifier = Modifier) {
         Box(
@@ -151,8 +152,7 @@
         isExternalOverscrollGesture: () -> Boolean,
     ): Modifier {
         return nestedScrollToScene(
-            layoutImpl = layoutImpl,
-            orientation = Orientation.Horizontal,
+            draggableHandler = layoutImpl.horizontalDraggableHandler,
             topOrLeftBehavior = leftBehavior,
             bottomOrRightBehavior = rightBehavior,
             isExternalOverscrollGesture = isExternalOverscrollGesture,
@@ -165,8 +165,7 @@
         isExternalOverscrollGesture: () -> Boolean,
     ): Modifier {
         return nestedScrollToScene(
-            layoutImpl = layoutImpl,
-            orientation = Orientation.Vertical,
+            draggableHandler = layoutImpl.verticalDraggableHandler,
             topOrLeftBehavior = topBehavior,
             bottomOrRightBehavior = bottomBehavior,
             isExternalOverscrollGesture = isExternalOverscrollGesture,
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index 7e6f3a8..61e9bb0 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -133,8 +133,8 @@
                 )
                 .apply { setContentsAndLayoutTargetSizeForTest(LAYOUT_SIZE) }
 
-        val draggableHandler = layoutImpl.draggableHandler(Orientation.Vertical)
-        val horizontalDraggableHandler = layoutImpl.draggableHandler(Orientation.Horizontal)
+        val draggableHandler = layoutImpl.verticalDraggableHandler
+        val horizontalDraggableHandler = layoutImpl.horizontalDraggableHandler
 
         var pointerInfoOwner: () -> PointersInfo = { pointersDown() }
 
@@ -143,8 +143,7 @@
             isExternalOverscrollGesture: Boolean = false,
         ) =
             NestedScrollHandlerImpl(
-                    layoutImpl = layoutImpl,
-                    orientation = draggableHandler.orientation,
+                    draggableHandler = draggableHandler,
                     topOrLeftBehavior = nestedScrollBehavior,
                     bottomOrRightBehavior = nestedScrollBehavior,
                     isExternalOverscrollGesture = { isExternalOverscrollGesture },
diff --git a/packages/SystemUI/tests/src/com/android/systemui/BatteryMeterDrawableTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/BatteryMeterDrawableTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/BatteryMeterDrawableTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/BatteryMeterDrawableTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/BootCompleteCacheTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/BootCompleteCacheTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/BootCompleteCacheTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/BootCompleteCacheTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/CameraAvailabilityListenerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/CameraAvailabilityListenerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/FakeCameraProtectionLoader.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/FakeCameraProtectionLoader.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/FakeCameraProtectionLoader.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/FakeCameraProtectionLoader.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
index 72163e4..f82c8b0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
@@ -31,7 +31,8 @@
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fake
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepository
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.util.mockito.whenever
@@ -62,7 +63,7 @@
     private val testScope = kosmos.testScope
     private val clock = FakeSystemClock()
     private val userRepository = FakeUserRepository()
-    private val mobileConnectionsRepository = kosmos.fakeMobileConnectionsRepository
+    private val mobileConnectionsRepository = kosmos.mobileConnectionsRepository
 
     private lateinit var underTest: AuthenticationRepository
 
@@ -110,7 +111,7 @@
                 .isEqualTo(AuthenticationMethodModel.None)
 
             currentSecurityMode = KeyguardSecurityModel.SecurityMode.SimPin
-            mobileConnectionsRepository.isAnySimSecure.value = true
+            mobileConnectionsRepository.fake.isAnySimSecure.value = true
             assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Sim)
             assertThat(underTest.getAuthenticationMethod()).isEqualTo(AuthenticationMethodModel.Sim)
         }
@@ -199,7 +200,7 @@
         }
 
     private fun setSecurityModeAndDispatchBroadcast(
-        securityMode: KeyguardSecurityModel.SecurityMode,
+        securityMode: KeyguardSecurityModel.SecurityMode
     ) {
         currentSecurityMode = securityMode
         dispatchBroadcast()
@@ -208,23 +209,15 @@
     private fun dispatchBroadcast() {
         fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
             context,
-            Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)
+            Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
         )
     }
 
     companion object {
         private val USER_INFOS =
             listOf(
-                UserInfo(
-                    /* id= */ 100,
-                    /* name= */ "First user",
-                    /* flags= */ 0,
-                ),
-                UserInfo(
-                    /* id= */ 101,
-                    /* name= */ "Second user",
-                    /* flags= */ 0,
-                ),
+                UserInfo(/* id= */ 100, /* name= */ "First user", /* flags= */ 0),
+                UserInfo(/* id= */ 101, /* name= */ "Second user", /* flags= */ 0),
             )
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogViewModelTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogViewModelTest.kt
index beb816c..32606e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogViewModelTest.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.bluetooth.qsdialog
 
 import android.bluetooth.BluetoothDevice
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.settingslib.bluetooth.LeAudioProfile
 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager
@@ -48,7 +48,7 @@
 import org.mockito.kotlin.whenever
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @OptIn(ExperimentalCoroutinesApi::class)
 class AudioSharingDialogViewModelTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryTest.kt
index c9e8813..acfe9dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingRepositoryTest.kt
@@ -17,8 +17,8 @@
 
 import android.bluetooth.BluetoothDevice
 import android.bluetooth.BluetoothLeBroadcastMetadata
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast
 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant
@@ -46,7 +46,7 @@
 
 @ExperimentalCoroutinesApi
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 class AudioSharingRepositoryTest : SysuiTestCase() {
     @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
index 566cd70..10bf523 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
@@ -35,7 +35,8 @@
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fake
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepository
 import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
 import com.android.systemui.testKosmos
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
@@ -78,7 +79,7 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        mobileConnectionsRepository = kosmos.fakeMobileConnectionsRepository
+        mobileConnectionsRepository = kosmos.mobileConnectionsRepository.fake
 
         overrideResource(R.string.lockscreen_emergency_call, MESSAGE_EMERGENCY_CALL)
         overrideResource(R.string.lockscreen_return_to_call, MESSAGE_RETURN_TO_CALL)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayScopeRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayScopeRepositoryImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayScopeRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayScopeRepositoryImplTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeMachineTest.java
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeMachineTest.java
index eb72f29..3347180 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeMachineTest.java
@@ -48,10 +48,10 @@
 import android.app.ActivityManager;
 import android.content.res.Configuration;
 import android.hardware.display.AmbientDisplayConfiguration;
-import androidx.test.annotation.UiThreadTest;
 import android.view.Display;
 
 import androidx.annotation.NonNull;
+import androidx.test.annotation.UiThreadTest;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt
new file mode 100644
index 0000000..aacfaed
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt
@@ -0,0 +1,229 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.dreams.ui.viewmodel
+
+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.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.scene.shared.model.SceneFamilies
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
+import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.shade.shared.flag.DualShade
+import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
+class DreamUserActionsViewModelTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    private lateinit var underTest: DreamUserActionsViewModel
+
+    @Before
+    fun setUp() {
+        underTest = kosmos.dreamUserActionsViewModel
+        underTest.activateIn(testScope)
+    }
+
+    @Test
+    @DisableFlags(DualShade.FLAG_NAME)
+    fun actions_singleShade() =
+        testScope.runTest {
+            val actions by collectLastValue(underTest.actions)
+
+            setUpState(
+                isShadeTouchable = true,
+                isDeviceUnlocked = false,
+                shadeMode = ShadeMode.Single,
+            )
+            assertThat(actions).isNotEmpty()
+            assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
+            assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
+            assertThat(actions?.get(Swipe.Down))
+                .isEqualTo(UserActionResult(Scenes.Shade, isIrreversible = true))
+
+            setUpState(
+                isShadeTouchable = false,
+                isDeviceUnlocked = false,
+                shadeMode = ShadeMode.Single,
+            )
+            assertThat(actions).isEmpty()
+
+            setUpState(
+                isShadeTouchable = true,
+                isDeviceUnlocked = true,
+                shadeMode = ShadeMode.Single,
+            )
+            assertThat(actions).isNotEmpty()
+            assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
+            assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
+            assertThat(actions?.get(Swipe.Down))
+                .isEqualTo(UserActionResult(Scenes.Shade, isIrreversible = true))
+        }
+
+    @Test
+    @DisableFlags(DualShade.FLAG_NAME)
+    fun actions_splitShade() =
+        testScope.runTest {
+            val actions by collectLastValue(underTest.actions)
+
+            setUpState(
+                isShadeTouchable = true,
+                isDeviceUnlocked = false,
+                shadeMode = ShadeMode.Split,
+            )
+            assertThat(actions).isNotEmpty()
+            assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
+            assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
+            assertThat(actions?.get(Swipe.Down))
+                .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true))
+
+            setUpState(
+                isShadeTouchable = false,
+                isDeviceUnlocked = false,
+                shadeMode = ShadeMode.Split,
+            )
+            assertThat(actions).isEmpty()
+
+            setUpState(
+                isShadeTouchable = true,
+                isDeviceUnlocked = true,
+                shadeMode = ShadeMode.Split,
+            )
+            assertThat(actions).isNotEmpty()
+            assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
+            assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
+            assertThat(actions?.get(Swipe.Down))
+                .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true))
+        }
+
+    @Test
+    @EnableFlags(DualShade.FLAG_NAME)
+    fun actions_dualShade() =
+        testScope.runTest {
+            val actions by collectLastValue(underTest.actions)
+
+            setUpState(
+                isShadeTouchable = true,
+                isDeviceUnlocked = false,
+                shadeMode = ShadeMode.Dual,
+            )
+            assertThat(actions).isNotEmpty()
+            assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
+            assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
+            assertThat(actions?.get(Swipe.Down))
+                .isEqualTo(
+                    UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true)
+                )
+
+            setUpState(
+                isShadeTouchable = false,
+                isDeviceUnlocked = false,
+                shadeMode = ShadeMode.Dual,
+            )
+            assertThat(actions).isEmpty()
+
+            setUpState(isShadeTouchable = true, isDeviceUnlocked = true, shadeMode = ShadeMode.Dual)
+            assertThat(actions).isNotEmpty()
+            assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
+            assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
+            assertThat(actions?.get(Swipe.Down))
+                .isEqualTo(
+                    UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true)
+                )
+        }
+
+    private fun TestScope.setUpState(
+        isShadeTouchable: Boolean,
+        isDeviceUnlocked: Boolean,
+        shadeMode: ShadeMode,
+    ) {
+        if (isShadeTouchable) {
+            kosmos.powerInteractor.setAwakeForTest()
+        } else {
+            kosmos.powerInteractor.setAsleepForTest()
+        }
+
+        if (isDeviceUnlocked) {
+            unlockDevice()
+        } else {
+            lockDevice()
+        }
+
+        if (shadeMode == ShadeMode.Dual) {
+            assertThat(DualShade.isEnabled).isTrue()
+        } else {
+            assertThat(DualShade.isEnabled).isFalse()
+            kosmos.fakeShadeRepository.setShadeLayoutWide(shadeMode == ShadeMode.Split)
+        }
+        runCurrent()
+    }
+
+    private fun TestScope.lockDevice() {
+        val deviceUnlockStatus by
+            collectLastValue(kosmos.deviceUnlockedInteractor.deviceUnlockStatus)
+
+        kosmos.fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+        assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
+        kosmos.sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
+        runCurrent()
+    }
+
+    private fun TestScope.unlockDevice() {
+        val deviceUnlockStatus by
+            collectLastValue(kosmos.deviceUnlockedInteractor.deviceUnlockStatus)
+
+        kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+            SuccessFingerprintAuthenticationStatus(0, true)
+        )
+        assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
+        kosmos.sceneInteractor.changeScene(Scenes.Gone, "reason")
+        runCurrent()
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModelTest.kt
index 5efb617..f6a6e54 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModelTest.kt
@@ -119,22 +119,6 @@
         }
 
     @Test
-    fun onLongClick_whenTileDoesNotHandleLongClick_playsFailureHaptics() =
-        testScope.runTest {
-            // WHEN the tile is long-clicked but the tile does not handle a long-click
-            val state = QSTile.State().apply { handlesLongClick = false }
-            qsTile.changeState(state)
-            underTest.setTileInteractionState(
-                TileHapticsViewModel.TileInteractionState.LONG_CLICKED
-            )
-            runCurrent()
-
-            // THEN the failure token plays
-            assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.FAILURE)
-            assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
-        }
-
-    @Test
     fun whenLaunchingFromClick_doesNotPlayHaptics() =
         testScope.runTest {
             // WHEN the tile is clicked and its action state changes accordingly
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt
index c646d8f..cc4c7c4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt
@@ -218,11 +218,11 @@
             simpleShortcutCategory(System, "System controls", "View recent apps"),
             simpleShortcutCategory(AppCategories, "Applications", "Calculator"),
             simpleShortcutCategory(AppCategories, "Applications", "Calendar"),
-            simpleShortcutCategory(AppCategories, "Applications", "Chrome"),
+            simpleShortcutCategory(AppCategories, "Applications", "Browser"),
             simpleShortcutCategory(AppCategories, "Applications", "Contacts"),
-            simpleShortcutCategory(AppCategories, "Applications", "Gmail"),
+            simpleShortcutCategory(AppCategories, "Applications", "Email"),
             simpleShortcutCategory(AppCategories, "Applications", "Maps"),
-            simpleShortcutCategory(AppCategories, "Applications", "Messages"),
+            simpleShortcutCategory(AppCategories, "Applications", "SMS"),
             simpleShortcutCategory(MultiTasking, "Recent apps", "Cycle forward through recent apps"),
         )
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/WorkLockActivityControllerTest.java
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
index 4e429c3..69fb03d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
@@ -47,7 +47,8 @@
 import com.android.systemui.keyguard.data.repository.BiometricType.UNDER_DISPLAY_FINGERPRINT
 import com.android.systemui.keyguard.shared.model.DevicePosture
 import com.android.systemui.res.R
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fake
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepository
 import com.android.systemui.statusbar.policy.DevicePostureController
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.FakeUserRepository
@@ -99,7 +100,7 @@
     private lateinit var devicePostureRepository: FakeDevicePostureRepository
     private lateinit var facePropertyRepository: FakeFacePropertyRepository
     private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
-    private val mobileConnectionsRepository = kosmos.fakeMobileConnectionsRepository
+    private val mobileConnectionsRepository = kosmos.mobileConnectionsRepository
 
     private lateinit var testDispatcher: TestDispatcher
     private lateinit var testScope: TestScope
@@ -142,7 +143,7 @@
             1,
             SensorStrength.STRONG,
             FingerprintSensorType.UDFPS_OPTICAL,
-            emptyMap()
+            emptyMap(),
         )
         verify(lockPatternUtils).registerStrongAuthTracker(strongAuthTracker.capture())
         verify(authController, times(2)).addCallback(authControllerCallback.capture())
@@ -247,7 +248,7 @@
     private fun deviceIsInPostureThatSupportsFaceAuth() {
         overrideResource(
             R.integer.config_face_auth_supported_posture,
-            DevicePostureController.DEVICE_POSTURE_FLIPPED
+            DevicePostureController.DEVICE_POSTURE_FLIPPED,
         )
         devicePostureRepository.setCurrentPosture(DevicePosture.FLIPPED)
     }
@@ -459,7 +460,7 @@
 
             biometricsAreEnabledBySettings()
             doNotDisableKeyguardAuthFeatures()
-            mobileConnectionsRepository.isAnySimSecure.value = false
+            mobileConnectionsRepository.fake.isAnySimSecure.value = false
             runCurrent()
 
             val isFaceAuthEnabledAndEnrolled by
@@ -467,7 +468,7 @@
 
             assertThat(isFaceAuthEnabledAndEnrolled).isTrue()
 
-            mobileConnectionsRepository.isAnySimSecure.value = true
+            mobileConnectionsRepository.fake.isAnySimSecure.value = true
             runCurrent()
 
             assertThat(isFaceAuthEnabledAndEnrolled).isFalse()
@@ -485,13 +486,13 @@
             doNotDisableKeyguardAuthFeatures()
             faceAuthIsStrongBiometric()
             biometricsAreEnabledBySettings()
-            mobileConnectionsRepository.isAnySimSecure.value = false
+            mobileConnectionsRepository.fake.isAnySimSecure.value = false
 
             onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
             onNonStrongAuthChanged(false, PRIMARY_USER_ID)
             assertThat(isFaceAuthCurrentlyAllowed).isTrue()
 
-            mobileConnectionsRepository.isAnySimSecure.value = true
+            mobileConnectionsRepository.fake.isAnySimSecure.value = true
             assertThat(isFaceAuthCurrentlyAllowed).isFalse()
         }
 
@@ -584,7 +585,7 @@
         testScope.runTest {
             overrideResource(
                 R.integer.config_face_auth_supported_posture,
-                DevicePostureController.DEVICE_POSTURE_UNKNOWN
+                DevicePostureController.DEVICE_POSTURE_UNKNOWN,
             )
 
             createBiometricSettingsRepository()
@@ -597,7 +598,7 @@
         testScope.runTest {
             overrideResource(
                 R.integer.config_face_auth_supported_posture,
-                DevicePostureController.DEVICE_POSTURE_FLIPPED
+                DevicePostureController.DEVICE_POSTURE_FLIPPED,
             )
 
             createBiometricSettingsRepository()
@@ -749,7 +750,7 @@
                 1,
                 SensorStrength.STRONG,
                 FingerprintSensorType.UDFPS_OPTICAL,
-                emptyMap()
+                emptyMap(),
             )
             // Non strong auth is not allowed now, FP is marked strong
             onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
@@ -761,7 +762,7 @@
                 1,
                 SensorStrength.CONVENIENCE,
                 FingerprintSensorType.UDFPS_OPTICAL,
-                emptyMap()
+                emptyMap(),
             )
             assertThat(isFingerprintCurrentlyAllowed).isFalse()
 
@@ -769,7 +770,7 @@
                 1,
                 SensorStrength.WEAK,
                 FingerprintSensorType.UDFPS_OPTICAL,
-                emptyMap()
+                emptyMap(),
             )
             assertThat(isFingerprintCurrentlyAllowed).isFalse()
         }
@@ -791,7 +792,7 @@
                 1,
                 SensorStrength.STRONG,
                 FingerprintSensorType.UDFPS_OPTICAL,
-                emptyMap()
+                emptyMap(),
             )
             // Non strong auth is not allowed now, FP is marked strong
             onStrongAuthChanged(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN, PRIMARY_USER_ID)
@@ -830,7 +831,7 @@
             UserInfo(
                 /* id= */ PRIMARY_USER_ID,
                 /* name= */ "primary user",
-                /* flags= */ UserInfo.FLAG_PRIMARY
+                /* flags= */ UserInfo.FLAG_PRIMARY,
             )
 
         private const val ANOTHER_USER_ID = 1
@@ -838,7 +839,7 @@
             UserInfo(
                 /* id= */ ANOTHER_USER_ID,
                 /* name= */ "another user",
-                /* flags= */ UserInfo.FLAG_PRIMARY
+                /* flags= */ UserInfo.FLAG_PRIMARY,
             )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryImplTest.kt
index 5a597fe..f537e32 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryImplTest.kt
@@ -50,7 +50,6 @@
         fakeSettings.userId = fakeUserTracker.userId
         underTest =
             KeyguardSmartspaceRepositoryImpl(
-                context = context,
                 secureSettings = fakeSettings,
                 userTracker = fakeUserTracker,
                 applicationScope = scope.backgroundScope,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt
index aee72de2..28ac169 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt
@@ -310,7 +310,7 @@
         // read during initialization to set up flows. Maybe there is a better way to handle that.
         underTest =
             KeyguardTouchHandlingInteractor(
-                appContext = mContext,
+                context = mContext,
                 scope = testScope.backgroundScope,
                 transitionInteractor = kosmos.keyguardTransitionInteractor,
                 repository = keyguardRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/power/PowerUITest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/power/PowerUITest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSSecurityFooterTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSSecurityFooterTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt
index 6fce108..ee2a1d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt
@@ -19,10 +19,10 @@
 import android.animation.AnimatorTestRule
 import android.content.Context
 import android.service.quicksettings.Tile
-import android.testing.AndroidTestingRunner
 import android.view.ContextThemeWrapper
 import android.view.View
 import android.widget.ImageView
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.annotation.UiThreadTest
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -35,7 +35,7 @@
 import org.junit.runner.RunWith
 
 /** Test for regression b/311121830 and b/323125376 */
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @UiThreadTest
 @SmallTest
 class QSIconViewImplTest_311121830 : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ActionExecutorTest.kt
similarity index 96%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ActionExecutorTest.kt
index a10d81f..1413204 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ActionExecutorTest.kt
@@ -20,8 +20,8 @@
 import android.content.Intent
 import android.os.Bundle
 import android.os.UserHandle
-import android.testing.AndroidTestingRunner
 import android.view.Window
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.mockito.argumentCaptor
@@ -39,7 +39,7 @@
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.verifyBlocking
 
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @SmallTest
 class ActionExecutorTest : SysuiTestCase() {
     private val scheduler = TestCoroutineScheduler()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt
index c5070286..84b7d10 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt
@@ -21,10 +21,10 @@
 import android.content.pm.PackageManager
 import android.content.pm.PackageManager.MATCH_ANY_USER
 import android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS
-import android.testing.AndroidTestingRunner
 import android.view.Display
 import android.view.IWindowManager
 import android.view.WindowManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argThat
@@ -43,7 +43,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class ScreenshotDetectionControllerTest {
 
     @Mock lateinit var windowManager: IWindowManager
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
index 77b5c91..d2eca0d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
@@ -23,9 +23,9 @@
 import android.net.Uri
 import android.os.UserHandle
 import android.os.UserManager
-import android.testing.AndroidTestingRunner
 import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER
 import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.internal.util.ScreenshotRequest
 import com.android.systemui.SysuiTestCase
@@ -49,7 +49,7 @@
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
 
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 class TakeScreenshotServiceTest : SysuiTestCase() {
 
     private val userManager = mock<UserManager> { on { isUserUnlocked } doReturn (true) }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java
index 9a8df33..cd55bb2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java
@@ -38,11 +38,11 @@
 import android.content.Intent;
 import android.os.Handler;
 import android.provider.Settings;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.View;
 import android.widget.TextView;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.keyguard.CarrierTextManager;
@@ -76,7 +76,7 @@
 import java.util.Arrays;
 import java.util.List;
 
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper
 @SmallTest
 public class ShadeCarrierGroupControllerTest extends LeakCheckedTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt
new file mode 100644
index 0000000..f192b59
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt
@@ -0,0 +1,159 @@
+/*
+ * 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.shade.domain.interactor
+
+import android.content.Context
+import android.content.res.Configuration
+import android.content.res.Resources
+import android.view.Display
+import android.view.WindowManager
+import android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.FakeDisplayWindowPropertiesRepository
+import com.android.systemui.display.shared.model.DisplayWindowProperties
+import com.android.systemui.scene.ui.view.WindowRootView
+import com.android.systemui.shade.data.repository.FakeShadePositionRepository
+import com.android.systemui.statusbar.phone.ConfigurationForwarder
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.advanceUntilIdle
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verifyNoMoreInteractions
+import org.mockito.kotlin.whenever
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class ShadeDisplaysInteractorTest : SysuiTestCase() {
+
+    private val shadeRootview = mock<WindowRootView>()
+    private val positionRepository = FakeShadePositionRepository()
+    private val defaultContext = mock<Context>()
+    private val secondaryContext = mock<Context>()
+    private val contextStore = FakeDisplayWindowPropertiesRepository()
+    private val testScope = TestScope(UnconfinedTestDispatcher())
+    private val configurationForwarder = mock<ConfigurationForwarder>()
+    private val defaultWm = mock<WindowManager>()
+    private val secondaryWm = mock<WindowManager>()
+    private val resources = mock<Resources>()
+    private val configuration = mock<Configuration>()
+    private val display = mock<Display>()
+
+    private val interactor =
+        ShadeDisplaysInteractor(
+            shadeRootview,
+            positionRepository,
+            defaultContext,
+            contextStore,
+            testScope,
+            configurationForwarder,
+            testScope.coroutineContext,
+        )
+
+    @Before
+    fun setup() {
+        whenever(shadeRootview.display).thenReturn(display)
+        whenever(display.displayId).thenReturn(0)
+
+        whenever(resources.configuration).thenReturn(configuration)
+        whenever(resources.configuration).thenReturn(configuration)
+
+        whenever(defaultContext.displayId).thenReturn(0)
+        whenever(defaultContext.getSystemService(any())).thenReturn(defaultWm)
+        whenever(defaultContext.resources).thenReturn(resources)
+        contextStore.insert(
+            DisplayWindowProperties(
+                displayId = 0,
+                windowType = TYPE_NOTIFICATION_SHADE,
+                context = defaultContext,
+                windowManager = defaultWm,
+                layoutInflater = mock(),
+            )
+        )
+
+        whenever(secondaryContext.displayId).thenReturn(1)
+        whenever(secondaryContext.getSystemService(any())).thenReturn(secondaryWm)
+        whenever(secondaryContext.resources).thenReturn(resources)
+        contextStore.insert(
+            DisplayWindowProperties(
+                displayId = 1,
+                windowType = TYPE_NOTIFICATION_SHADE,
+                context = secondaryContext,
+                windowManager = secondaryWm,
+                layoutInflater = mock(),
+            )
+        )
+    }
+
+    @Test
+    fun start_shadeInCorrectPosition_notAddedOrRemoved() {
+        whenever(display.displayId).thenReturn(0)
+        positionRepository.setDisplayId(0)
+        interactor.start()
+        testScope.advanceUntilIdle()
+
+        verifyNoMoreInteractions(defaultWm)
+        verifyNoMoreInteractions(secondaryWm)
+    }
+
+    @Test
+    fun start_shadeInWrongPosition_changes() {
+        whenever(display.displayId).thenReturn(0)
+        positionRepository.setDisplayId(1)
+        interactor.start()
+        testScope.advanceUntilIdle()
+
+        verify(defaultWm).removeView(eq(shadeRootview))
+        verify(secondaryWm).addView(eq(shadeRootview), any())
+    }
+
+    @Test
+    fun start_shadePositionChanges_removedThenAdded() {
+        whenever(display.displayId).thenReturn(0)
+        positionRepository.setDisplayId(0)
+        interactor.start()
+        testScope.advanceUntilIdle()
+
+        positionRepository.setDisplayId(1)
+        testScope.advanceUntilIdle()
+
+        verify(defaultWm).removeView(eq(shadeRootview))
+        verify(secondaryWm).addView(eq(shadeRootview), any())
+    }
+
+    @Test
+    fun start_shadePositionChanges_newConfigPropagated() {
+        whenever(display.displayId).thenReturn(0)
+        positionRepository.setDisplayId(0)
+        interactor.start()
+        testScope.advanceUntilIdle()
+
+        positionRepository.setDisplayId(1)
+        testScope.advanceUntilIdle()
+
+        verify(configurationForwarder).onConfigurationChanged(eq(configuration))
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/CallbackHandlerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
index c8ef663..e974c2e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
@@ -30,13 +30,13 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
-import android.testing.AndroidTestingRunner;
 import android.util.FloatProperty;
 import android.util.Property;
 import android.view.View;
 import android.view.animation.Interpolator;
 
 import androidx.test.annotation.UiThreadTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.app.animation.Interpolators;
@@ -51,7 +51,7 @@
 import org.junit.runner.RunWith;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @UiThreadTest
 public class PropertyAnimatorTest extends SysuiTestCase {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index e21a005..4ef9792 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -68,12 +68,12 @@
 import android.service.notification.NotificationListenerService.Ranking;
 import android.service.notification.NotificationListenerService.RankingMap;
 import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 
 import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.statusbar.IStatusBarService;
@@ -118,7 +118,7 @@
 import java.util.Map;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper
 public class NotifCollectionTest extends SysuiTestCase {
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index 9613f76..2c488e3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -28,7 +28,6 @@
 import com.android.systemui.statusbar.NotificationRemoteInputManager
 import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor
 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
-import com.android.systemui.statusbar.notification.HeadsUpManagerPhone
 import com.android.systemui.statusbar.notification.NotifPipelineFlags
 import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
@@ -52,6 +51,7 @@
 import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider
 import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback
 import com.android.systemui.statusbar.phone.NotificationGroupTestHelper
+import com.android.systemui.statusbar.policy.BaseHeadsUpManager
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
 import com.android.systemui.testKosmos
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -101,7 +101,7 @@
 
     private val notifPipeline: NotifPipeline = mock()
     private val logger = HeadsUpCoordinatorLogger(logcatLogBuffer(), verbose = true)
-    private val headsUpManager: HeadsUpManagerPhone = mock()
+    private val headsUpManager: BaseHeadsUpManager = mock()
     private val headsUpViewBinder: HeadsUpViewBinder = mock()
     private val visualInterruptionDecisionProvider: VisualInterruptionDecisionProvider = mock()
     private val remoteInputManager: NotificationRemoteInputManager = mock()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt
index fd41921..371e1c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt
@@ -16,13 +16,13 @@
 package com.android.systemui.statusbar.notification.row
 
 import android.content.Context
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import android.util.AttributeSet
 import android.view.View
 import android.widget.Button
 import android.widget.FrameLayout
 import android.widget.LinearLayout
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED
@@ -38,7 +38,7 @@
 
 /** Tests for [NotifLayoutInflaterFactory] */
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @RunWithLooper
 class NotifLayoutInflaterFactoryTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
index 3669e3d..b8745b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
@@ -21,12 +21,12 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.when;
 
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewGroup;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -47,7 +47,7 @@
 import org.mockito.junit.MockitoRule;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class NotificationSectionsManagerTest extends SysuiTestCase {
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index f6d439a..5a77f3d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
 
 import android.os.ParcelUuid
-import android.telephony.SubscriptionManager
 import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
 import android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING
 import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
@@ -25,91 +24,78 @@
 import androidx.test.filters.SmallTest
 import com.android.settingslib.mobile.MobileMappings
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
-import com.android.systemui.log.table.logcatTableLogBuffer
+import com.android.systemui.flags.fake
+import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runCurrent
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fake
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepositoryLogbufferName
 import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
-import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.pipeline.shared.data.repository.connectivityRepository
+import com.android.systemui.statusbar.pipeline.shared.data.repository.fake
 import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
 import com.android.systemui.testKosmos
 import com.android.systemui.util.CarrierConfigTracker
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import java.util.UUID
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.advanceTimeBy
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class MobileIconsInteractorTest : SysuiTestCase() {
-    private val kosmos = testKosmos()
-
-    private lateinit var underTest: MobileIconsInteractor
-    private lateinit var connectivityRepository: FakeConnectivityRepository
-    private lateinit var connectionsRepository: FakeMobileConnectionsRepository
-    private val userSetupRepository = FakeUserSetupRepository()
-    private val mobileMappingsProxy = FakeMobileMappingsProxy()
-    private val flags =
-        FakeFeatureFlagsClassic().apply {
-            set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, true)
+    private val kosmos by lazy {
+        testKosmos().apply {
+            mobileConnectionsRepositoryLogbufferName = "MobileIconsInteractorTest"
+            mobileConnectionsRepository.fake.run {
+                setMobileConnectionRepositoryMap(
+                    mapOf(
+                        SUB_1_ID to CONNECTION_1,
+                        SUB_2_ID to CONNECTION_2,
+                        SUB_3_ID to CONNECTION_3,
+                        SUB_4_ID to CONNECTION_4,
+                    )
+                )
+                setActiveMobileDataSubscriptionId(SUB_1_ID)
+            }
+            featureFlagsClassic.fake.set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, true)
         }
+    }
 
-    private val testDispatcher = StandardTestDispatcher()
-    private val testScope = TestScope(testDispatcher)
+    // shortcut rename
+    private val Kosmos.connectionsRepository by Fixture { mobileConnectionsRepository.fake }
 
-    private val tableLogBuffer = logcatTableLogBuffer(kosmos, "MobileIconsInteractorTest")
+    private val Kosmos.carrierConfigTracker by Fixture { mock<CarrierConfigTracker>() }
 
-    @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
-        connectivityRepository = FakeConnectivityRepository()
-
-        connectionsRepository = FakeMobileConnectionsRepository(mobileMappingsProxy, tableLogBuffer)
-        connectionsRepository.setMobileConnectionRepositoryMap(
-            mapOf(
-                SUB_1_ID to CONNECTION_1,
-                SUB_2_ID to CONNECTION_2,
-                SUB_3_ID to CONNECTION_3,
-                SUB_4_ID to CONNECTION_4,
-            )
+    private val Kosmos.underTest by Fixture {
+        MobileIconsInteractorImpl(
+            mobileConnectionsRepository,
+            carrierConfigTracker,
+            tableLogger = mock(),
+            connectivityRepository,
+            FakeUserSetupRepository(),
+            testScope.backgroundScope,
+            context,
+            featureFlagsClassic,
         )
-        connectionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID)
-
-        underTest =
-            MobileIconsInteractorImpl(
-                connectionsRepository,
-                carrierConfigTracker,
-                tableLogger = mock(),
-                connectivityRepository,
-                userSetupRepository,
-                testScope.backgroundScope,
-                context,
-                flags,
-            )
     }
 
     @Test
     fun filteredSubscriptions_default() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.filteredSubscriptions)
 
             assertThat(latest).isEqualTo(listOf<SubscriptionModel>())
@@ -118,7 +104,7 @@
     // Based on the logic from the old pipeline, we'll never filter subs when there are more than 2
     @Test
     fun filteredSubscriptions_moreThanTwo_doesNotFilter() =
-        testScope.runTest {
+        kosmos.runTest {
             connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP, SUB_4_OPP))
             connectionsRepository.setActiveMobileDataSubscriptionId(SUB_4_ID)
 
@@ -129,7 +115,7 @@
 
     @Test
     fun filteredSubscriptions_nonOpportunistic_updatesWithMultipleSubs() =
-        testScope.runTest {
+        kosmos.runTest {
             connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
 
             val latest by collectLastValue(underTest.filteredSubscriptions)
@@ -139,7 +125,7 @@
 
     @Test
     fun filteredSubscriptions_opportunistic_differentGroups_doesNotFilter() =
-        testScope.runTest {
+        kosmos.runTest {
             connectionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
             connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
 
@@ -150,7 +136,7 @@
 
     @Test
     fun filteredSubscriptions_opportunistic_nonGrouped_doesNotFilter() =
-        testScope.runTest {
+        kosmos.runTest {
             val (sub1, sub2) =
                 createSubscriptionPair(
                     subscriptionIds = Pair(SUB_1_ID, SUB_2_ID),
@@ -167,7 +153,7 @@
 
     @Test
     fun filteredSubscriptions_opportunistic_grouped_configFalse_showsActive_3() =
-        testScope.runTest {
+        kosmos.runTest {
             val (sub3, sub4) =
                 createSubscriptionPair(
                     subscriptionIds = Pair(SUB_3_ID, SUB_4_ID),
@@ -187,7 +173,7 @@
 
     @Test
     fun filteredSubscriptions_opportunistic_grouped_configFalse_showsActive_4() =
-        testScope.runTest {
+        kosmos.runTest {
             val (sub3, sub4) =
                 createSubscriptionPair(
                     subscriptionIds = Pair(SUB_3_ID, SUB_4_ID),
@@ -207,7 +193,7 @@
 
     @Test
     fun filteredSubscriptions_oneOpportunistic_grouped_configTrue_showsPrimary_active_1() =
-        testScope.runTest {
+        kosmos.runTest {
             val (sub1, sub3) =
                 createSubscriptionPair(
                     subscriptionIds = Pair(SUB_1_ID, SUB_3_ID),
@@ -228,7 +214,7 @@
 
     @Test
     fun filteredSubscriptions_oneOpportunistic_grouped_configTrue_showsPrimary_nonActive_1() =
-        testScope.runTest {
+        kosmos.runTest {
             val (sub1, sub3) =
                 createSubscriptionPair(
                     subscriptionIds = Pair(SUB_1_ID, SUB_3_ID),
@@ -249,7 +235,7 @@
 
     @Test
     fun filteredSubscriptions_vcnSubId_agreesWithActiveSubId_usesActiveAkaVcnSub() =
-        testScope.runTest {
+        kosmos.runTest {
             val (sub1, sub3) =
                 createSubscriptionPair(
                     subscriptionIds = Pair(SUB_1_ID, SUB_3_ID),
@@ -258,7 +244,7 @@
                 )
             connectionsRepository.setSubscriptions(listOf(sub1, sub3))
             connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
-            connectivityRepository.vcnSubId.value = SUB_3_ID
+            kosmos.connectivityRepository.fake.vcnSubId.value = SUB_3_ID
             whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
                 .thenReturn(false)
 
@@ -269,7 +255,7 @@
 
     @Test
     fun filteredSubscriptions_vcnSubId_disagreesWithActiveSubId_usesVcnSub() =
-        testScope.runTest {
+        kosmos.runTest {
             val (sub1, sub3) =
                 createSubscriptionPair(
                     subscriptionIds = Pair(SUB_1_ID, SUB_3_ID),
@@ -278,7 +264,7 @@
                 )
             connectionsRepository.setSubscriptions(listOf(sub1, sub3))
             connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
-            connectivityRepository.vcnSubId.value = SUB_1_ID
+            kosmos.connectivityRepository.fake.vcnSubId.value = SUB_1_ID
             whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
                 .thenReturn(false)
 
@@ -289,9 +275,9 @@
 
     @Test
     fun filteredSubscriptions_doesNotFilterProvisioningWhenFlagIsFalse() =
-        testScope.runTest {
+        kosmos.runTest {
             // GIVEN the flag is false
-            flags.set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, false)
+            featureFlagsClassic.fake.set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, false)
 
             // GIVEN 1 sub that is in PROFILE_CLASS_PROVISIONING
             val sub1 =
@@ -313,7 +299,7 @@
 
     @Test
     fun filteredSubscriptions_filtersOutProvisioningSubs() =
-        testScope.runTest {
+        kosmos.runTest {
             val sub1 =
                 SubscriptionModel(
                     subscriptionId = SUB_1_ID,
@@ -326,7 +312,7 @@
                     subscriptionId = SUB_2_ID,
                     isOpportunistic = false,
                     carrierName = "Carrier 2",
-                    profileClass = SubscriptionManager.PROFILE_CLASS_PROVISIONING,
+                    profileClass = PROFILE_CLASS_PROVISIONING,
                 )
 
             connectionsRepository.setSubscriptions(listOf(sub1, sub2))
@@ -339,7 +325,7 @@
     /** Note: I'm not sure if this will ever be the case, but we can test it at least */
     @Test
     fun filteredSubscriptions_filtersOutProvisioningSubsBeforeOpportunistic() =
-        testScope.runTest {
+        kosmos.runTest {
             // This is a contrived test case, where the active subId is the one that would
             // also be filtered by opportunistic filtering.
 
@@ -376,7 +362,7 @@
 
     @Test
     fun filteredSubscriptions_groupedPairAndNonProvisioned_groupedFilteringStillHappens() =
-        testScope.runTest {
+        kosmos.runTest {
             // Grouped filtering only happens when the list of subs is length 2. In this case
             // we'll show that filtering of provisioning subs happens before, and thus grouped
             // filtering happens even though the unfiltered list is length 3
@@ -406,7 +392,7 @@
 
     @Test
     fun filteredSubscriptions_subNotExclusivelyNonTerrestrial_hasSub() =
-        testScope.runTest {
+        kosmos.runTest {
             val notExclusivelyNonTerrestrialSub =
                 SubscriptionModel(
                     isExclusivelyNonTerrestrial = false,
@@ -424,7 +410,7 @@
 
     @Test
     fun filteredSubscriptions_subExclusivelyNonTerrestrial_doesNotHaveSub() =
-        testScope.runTest {
+        kosmos.runTest {
             val exclusivelyNonTerrestrialSub =
                 SubscriptionModel(
                     isExclusivelyNonTerrestrial = true,
@@ -442,7 +428,7 @@
 
     @Test
     fun filteredSubscription_mixOfExclusivelyNonTerrestrialAndOther_hasOtherSubsOnly() =
-        testScope.runTest {
+        kosmos.runTest {
             val exclusivelyNonTerrestrialSub =
                 SubscriptionModel(
                     isExclusivelyNonTerrestrial = true,
@@ -476,7 +462,7 @@
 
     @Test
     fun filteredSubscriptions_exclusivelyNonTerrestrialSub_andOpportunistic_bothFiltersHappen() =
-        testScope.runTest {
+        kosmos.runTest {
             // Exclusively non-terrestrial sub
             val exclusivelyNonTerrestrialSub =
                 SubscriptionModel(
@@ -507,7 +493,7 @@
 
     @Test
     fun activeDataConnection_turnedOn() =
-        testScope.runTest {
+        kosmos.runTest {
             CONNECTION_1.setDataEnabled(true)
 
             val latest by collectLastValue(underTest.activeDataConnectionHasDataEnabled)
@@ -517,7 +503,7 @@
 
     @Test
     fun activeDataConnection_turnedOff() =
-        testScope.runTest {
+        kosmos.runTest {
             CONNECTION_1.setDataEnabled(true)
             val latest by collectLastValue(underTest.activeDataConnectionHasDataEnabled)
 
@@ -528,7 +514,7 @@
 
     @Test
     fun activeDataConnection_invalidSubId() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.activeDataConnectionHasDataEnabled)
 
             connectionsRepository.setActiveMobileDataSubscriptionId(INVALID_SUBSCRIPTION_ID)
@@ -539,7 +525,7 @@
 
     @Test
     fun failedConnection_default_validated_notFailed() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.isDefaultConnectionFailed)
 
             connectionsRepository.mobileIsDefault.value = true
@@ -550,7 +536,7 @@
 
     @Test
     fun failedConnection_notDefault_notValidated_notFailed() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.isDefaultConnectionFailed)
 
             connectionsRepository.mobileIsDefault.value = false
@@ -561,7 +547,7 @@
 
     @Test
     fun failedConnection_default_notValidated_failed() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.isDefaultConnectionFailed)
 
             connectionsRepository.mobileIsDefault.value = true
@@ -572,7 +558,7 @@
 
     @Test
     fun failedConnection_carrierMergedDefault_notValidated_failed() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.isDefaultConnectionFailed)
 
             connectionsRepository.hasCarrierMergedConnection.value = true
@@ -584,7 +570,7 @@
     /** Regression test for b/275076959. */
     @Test
     fun failedConnection_dataSwitchInSameGroup_notFailed() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.isDefaultConnectionFailed)
 
             connectionsRepository.mobileIsDefault.value = true
@@ -602,7 +588,7 @@
 
     @Test
     fun failedConnection_dataSwitchNotInSameGroup_isFailed() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.isDefaultConnectionFailed)
 
             connectionsRepository.mobileIsDefault.value = true
@@ -618,7 +604,7 @@
 
     @Test
     fun alwaysShowDataRatIcon_configHasTrue() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.alwaysShowDataRatIcon)
 
             val config = MobileMappings.Config()
@@ -630,7 +616,7 @@
 
     @Test
     fun alwaysShowDataRatIcon_configHasFalse() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.alwaysShowDataRatIcon)
 
             val config = MobileMappings.Config()
@@ -642,7 +628,7 @@
 
     @Test
     fun alwaysUseCdmaLevel_configHasTrue() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.alwaysUseCdmaLevel)
 
             val config = MobileMappings.Config()
@@ -654,7 +640,7 @@
 
     @Test
     fun alwaysUseCdmaLevel_configHasFalse() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.alwaysUseCdmaLevel)
 
             val config = MobileMappings.Config()
@@ -666,7 +652,7 @@
 
     @Test
     fun isSingleCarrier_zeroSubscriptions_false() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.isSingleCarrier)
 
             connectionsRepository.setSubscriptions(emptyList())
@@ -676,7 +662,7 @@
 
     @Test
     fun isSingleCarrier_oneSubscription_true() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.isSingleCarrier)
 
             connectionsRepository.setSubscriptions(listOf(SUB_1))
@@ -686,7 +672,7 @@
 
     @Test
     fun isSingleCarrier_twoSubscriptions_false() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.isSingleCarrier)
 
             connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
@@ -696,7 +682,7 @@
 
     @Test
     fun isSingleCarrier_updates() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.isSingleCarrier)
 
             connectionsRepository.setSubscriptions(listOf(SUB_1))
@@ -708,7 +694,7 @@
 
     @Test
     fun mobileIsDefault_mobileFalseAndCarrierMergedFalse_false() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.mobileIsDefault)
 
             connectionsRepository.mobileIsDefault.value = false
@@ -719,7 +705,7 @@
 
     @Test
     fun mobileIsDefault_mobileTrueAndCarrierMergedFalse_true() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.mobileIsDefault)
 
             connectionsRepository.mobileIsDefault.value = true
@@ -731,7 +717,7 @@
     /** Regression test for b/272586234. */
     @Test
     fun mobileIsDefault_mobileFalseAndCarrierMergedTrue_true() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.mobileIsDefault)
 
             connectionsRepository.mobileIsDefault.value = false
@@ -742,7 +728,7 @@
 
     @Test
     fun mobileIsDefault_updatesWhenRepoUpdates() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.mobileIsDefault)
 
             connectionsRepository.mobileIsDefault.value = true
@@ -760,7 +746,7 @@
 
     @Test
     fun dataSwitch_inSameGroup_validatedMatchesPreviousValue_expiresAfter2s() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.isDefaultConnectionFailed)
 
             connectionsRepository.mobileIsDefault.value = true
@@ -774,17 +760,17 @@
 
             // After 1s, the force validation bit is still present, so the connection is not marked
             // as failed
-            advanceTimeBy(1000)
+            testScope.advanceTimeBy(1000)
             assertThat(latest).isFalse()
 
             // After 2s, the force validation expires so the connection updates to failed
-            advanceTimeBy(1001)
+            testScope.advanceTimeBy(1001)
             assertThat(latest).isTrue()
         }
 
     @Test
     fun dataSwitch_inSameGroup_notValidated_immediatelyMarkedAsFailed() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.isDefaultConnectionFailed)
 
             connectionsRepository.mobileIsDefault.value = true
@@ -798,7 +784,7 @@
 
     @Test
     fun dataSwitch_loseValidation_thenSwitchHappens_clearsForcedBit() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.isDefaultConnectionFailed)
 
             // GIVEN the network starts validated
@@ -819,17 +805,17 @@
             // THEN the forced validation bit is still used...
             assertThat(latest).isFalse()
 
-            advanceTimeBy(1000)
+            testScope.advanceTimeBy(1000)
             assertThat(latest).isFalse()
 
             // ... but expires after 2s
-            advanceTimeBy(1001)
+            testScope.advanceTimeBy(1001)
             assertThat(latest).isTrue()
         }
 
     @Test
     fun dataSwitch_whileAlreadyForcingValidation_resetsClock() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.isDefaultConnectionFailed)
             connectionsRepository.mobileIsDefault.value = true
             connectionsRepository.defaultConnectionIsValidated.value = true
@@ -837,7 +823,7 @@
 
             connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
 
-            advanceTimeBy(1000)
+            testScope.advanceTimeBy(1000)
 
             // WHEN another change in same group event happens
             connectionsRepository.activeSubChangedInGroupEvent.emit(Unit)
@@ -847,37 +833,37 @@
             // THEN the forced validation remains for exactly 2 more seconds from now
 
             // 1.500s from second event
-            advanceTimeBy(1500)
+            testScope.advanceTimeBy(1500)
             assertThat(latest).isFalse()
 
             // 2.001s from the second event
-            advanceTimeBy(501)
+            testScope.advanceTimeBy(501)
             assertThat(latest).isTrue()
         }
 
     @Test
     fun isForceHidden_repoHasMobileHidden_true() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.isForceHidden)
 
-            connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.MOBILE))
+            kosmos.connectivityRepository.fake.setForceHiddenIcons(setOf(ConnectivitySlot.MOBILE))
 
             assertThat(latest).isTrue()
         }
 
     @Test
     fun isForceHidden_repoDoesNotHaveMobileHidden_false() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.isForceHidden)
 
-            connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI))
+            kosmos.connectivityRepository.fake.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI))
 
             assertThat(latest).isFalse()
         }
 
     @Test
     fun iconInteractor_cachedPerSubId() =
-        testScope.runTest {
+        kosmos.runTest {
             val interactor1 = underTest.getMobileConnectionInteractorForSubId(SUB_1_ID)
             val interactor2 = underTest.getMobileConnectionInteractorForSubId(SUB_1_ID)
 
@@ -887,7 +873,7 @@
 
     @Test
     fun deviceBasedEmergencyMode_emergencyCallsOnly_followsDeviceServiceStateFromRepo() =
-        testScope.runTest {
+        kosmos.runTest {
             val latest by collectLastValue(underTest.isDeviceInEmergencyCallsOnlyMode)
 
             connectionsRepository.isDeviceEmergencyCallCapable.value = true
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
index 9a862fc..c5eed73 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
@@ -24,11 +24,19 @@
 import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.shade.domain.interactor.shadeInteractor
 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.GroupMembershipManagerImpl
 import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
+import com.android.systemui.statusbar.phone.keyguardBypassController
 import com.android.systemui.statusbar.policy.HeadsUpManagerTestUtil.createFullScreenIntentEntry
+import com.android.systemui.testKosmos
 import com.android.systemui.util.concurrency.FakeExecutor
+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
@@ -48,6 +56,7 @@
 @RunWith(AndroidJUnit4::class)
 @EnableFlags(NotificationThrottleHun.FLAG_NAME)
 class AvalancheControllerTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
 
     // For creating mocks
     @get:Rule var rule: MockitoRule = MockitoJUnit.rule()
@@ -61,7 +70,6 @@
     @Mock private val mAccessibilityMgr: AccessibilityManagerWrapper? = null
     private val mUiEventLoggerFake = UiEventLoggerFake()
     @Mock private lateinit var mHeadsUpManagerLogger: HeadsUpManagerLogger
-
     @Mock private lateinit var mBgHandler: Handler
 
     private val mLogger = Mockito.spy(HeadsUpManagerLogger(logcatLogBuffer()))
@@ -76,26 +84,33 @@
         Mockito.`when`(
                 mAccessibilityMgr!!.getRecommendedTimeoutMillis(
                     ArgumentMatchers.anyInt(),
-                    ArgumentMatchers.anyInt()
+                    ArgumentMatchers.anyInt(),
                 )
             )
             .then { i: InvocationOnMock -> i.getArgument(0) }
 
         // Initialize AvalancheController and TestableHeadsUpManager during setUp instead of
         // declaration, where mocks are null
-        mAvalancheController = AvalancheController(dumpManager, mUiEventLoggerFake,
-                mHeadsUpManagerLogger, mBgHandler)
+        mAvalancheController =
+            AvalancheController(dumpManager, mUiEventLoggerFake, mHeadsUpManagerLogger, mBgHandler)
 
         testableHeadsUpManager =
             TestableHeadsUpManager(
                 mContext,
                 mLogger,
+                kosmos.statusBarStateController,
+                kosmos.keyguardBypassController,
+                GroupMembershipManagerImpl(),
+                kosmos.visualStabilityProvider,
+                kosmos.configurationController,
                 mExecutor,
                 mGlobalSettings,
                 mSystemClock,
                 mAccessibilityMgr,
                 mUiEventLoggerFake,
-                mAvalancheController
+                JavaAdapter(kosmos.testScope),
+                kosmos.shadeInteractor,
+                mAvalancheController,
             )
     }
 
@@ -270,7 +285,6 @@
         assertThat(mAvalancheController.headsUpEntryShowing).isEqualTo(nextEntry)
     }
 
-
     @Test
     fun testDelete_deleteSecondToLastEntry_showingEntryKeyBecomesPreviousHunKey() {
         mAvalancheController.previousHunKey = ""
@@ -305,7 +319,7 @@
         mAvalancheController.delete(showingEntry, runnableMock!!, "testLabel")
 
         // Next entry is shown
-        assertThat(mAvalancheController.previousHunKey).isEqualTo("");
+        assertThat(mAvalancheController.previousHunKey).isEqualTo("")
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
index 89aa670..abb3e6e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
@@ -35,6 +35,8 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
+
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.app.Person;
@@ -49,15 +51,21 @@
 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.res.R;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
+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.render.GroupMembershipManagerImpl;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
 import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.settings.FakeGlobalSettings;
 import com.android.systemui.util.time.FakeSystemClock;
 
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -73,7 +81,10 @@
 @SmallTest
 @TestableLooper.RunWithLooper
 @RunWith(ParameterizedAndroidJunit4.class)
+// TODO(b/378142453): Merge this with BaseHeadsUpManagerTest.
 public class BaseHeadsUpManagerTest extends SysuiTestCase {
+    protected KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
+
     @Rule
     public MockitoRule rule = MockitoJUnit.rule();
 
@@ -85,6 +96,7 @@
     private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer()));
     @Mock private Handler mBgHandler;
     @Mock private DumpManager dumpManager;
+    @Mock private ShadeInteractor mShadeInteractor;
     private AvalancheController mAvalancheController;
 
     @Mock private AccessibilityManagerWrapper mAccessibilityMgr;
@@ -108,8 +120,22 @@
     }
 
     private BaseHeadsUpManager createHeadsUpManager() {
-        return new TestableHeadsUpManager(mContext, mLogger, mExecutor, mGlobalSettings,
-                mSystemClock, mAccessibilityMgr, mUiEventLoggerFake, mAvalancheController);
+        return new TestableHeadsUpManager(
+                mContext,
+                mLogger,
+                mKosmos.getStatusBarStateController(),
+                mKosmos.getKeyguardBypassController(),
+                new GroupMembershipManagerImpl(),
+                mKosmos.getVisualStabilityProvider(),
+                mKosmos.getConfigurationController(),
+                mExecutor,
+                mGlobalSettings,
+                mSystemClock,
+                mAccessibilityMgr,
+                mUiEventLoggerFake,
+                new JavaAdapter(mKosmos.getTestScope()),
+                mShadeInteractor,
+                mAvalancheController);
     }
 
     private NotificationEntry createStickyEntry(int id) {
@@ -152,6 +178,8 @@
         super.SysuiSetup();
         mAvalancheController = new AvalancheController(dumpManager, mUiEventLoggerFake, mLogger,
                 mBgHandler);
+        when(mShadeInteractor.isAnyExpanded()).thenReturn(MutableStateFlow(true));
+        when(mKosmos.getKeyguardBypassController().getBypassEnabled()).thenReturn(false);
     }
 
     @Test
@@ -298,46 +326,6 @@
         verify(mLogger, times(1)).logNotificationActuallyRemoved(eq(notifEntry));
     }
 
-    @Test
-    public void testShouldHeadsUpBecomePinned_hasFSI_notUnpinned_true() {
-        final BaseHeadsUpManager hum = createHeadsUpManager();
-        final NotificationEntry notifEntry =
-                HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id = */ 0, mContext);
-
-        // Add notifEntry to ANM mAlertEntries map and make it NOT unpinned
-        hum.showNotification(notifEntry);
-
-        final BaseHeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(
-                notifEntry.getKey());
-        headsUpEntry.mWasUnpinned = false;
-
-        assertTrue(hum.shouldHeadsUpBecomePinned(notifEntry));
-    }
-
-    @Test
-    public void testShouldHeadsUpBecomePinned_wasUnpinned_false() {
-        final BaseHeadsUpManager hum = createHeadsUpManager();
-        final NotificationEntry notifEntry =
-                HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id = */ 0, mContext);
-
-        // Add notifEntry to ANM mAlertEntries map and make it unpinned
-        hum.showNotification(notifEntry);
-
-        final BaseHeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(
-                notifEntry.getKey());
-        headsUpEntry.mWasUnpinned = true;
-
-        assertFalse(hum.shouldHeadsUpBecomePinned(notifEntry));
-    }
-
-    @Test
-    public void testShouldHeadsUpBecomePinned_noFSI_false() {
-        final BaseHeadsUpManager hum = createHeadsUpManager();
-        final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
-
-        assertFalse(hum.shouldHeadsUpBecomePinned(entry));
-    }
-
 
     @Test
     public void testShowNotification_autoDismissesIncludingTouchAcceptanceDelay() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryStateNotifierTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BatteryStateNotifierTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryStateNotifierTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BatteryStateNotifierTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
index 1915e8e..8ebdbaa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
@@ -15,7 +15,6 @@
  */
 package com.android.systemui.statusbar.policy
 
-import android.content.Context
 import android.os.Handler
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.FlagsParameterization
@@ -26,27 +25,19 @@
 import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.logcatLogBuffer
-import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.res.R
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.FakeStatusBarStateController
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
 import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
-import com.android.systemui.statusbar.notification.HeadsUpManagerPhone
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.testKosmos
-import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.concurrency.mockExecutorHandler
 import com.android.systemui.util.kotlin.JavaAdapter
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.settings.GlobalSettings
-import com.android.systemui.util.time.SystemClock
 import com.google.common.truth.Truth.assertThat
 import junit.framework.Assert
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -78,7 +69,7 @@
 
     @Mock private lateinit var mVSProvider: VisualStabilityProvider
 
-    @Mock private lateinit var mStatusBarStateController: StatusBarStateController
+    val statusBarStateController = FakeStatusBarStateController()
 
     @Mock private lateinit var mBypassController: KeyguardBypassController
 
@@ -97,61 +88,16 @@
 
     @Mock private lateinit var mBgHandler: Handler
 
-    private class TestableHeadsUpManagerPhone(
-        context: Context,
-        headsUpManagerLogger: HeadsUpManagerLogger,
-        groupManager: GroupMembershipManager,
-        visualStabilityProvider: VisualStabilityProvider,
-        statusBarStateController: StatusBarStateController,
-        keyguardBypassController: KeyguardBypassController,
-        configurationController: ConfigurationController,
-        globalSettings: GlobalSettings,
-        systemClock: SystemClock,
-        executor: DelayableExecutor,
-        accessibilityManagerWrapper: AccessibilityManagerWrapper,
-        uiEventLogger: UiEventLogger,
-        javaAdapter: JavaAdapter,
-        shadeInteractor: ShadeInteractor,
-        avalancheController: AvalancheController
-    ) :
-        HeadsUpManagerPhone(
-            context,
-            headsUpManagerLogger,
-            statusBarStateController,
-            keyguardBypassController,
-            groupManager,
-            visualStabilityProvider,
-            configurationController,
-            mockExecutorHandler(executor),
-            globalSettings,
-            systemClock,
-            executor,
-            accessibilityManagerWrapper,
-            uiEventLogger,
-            javaAdapter,
-            shadeInteractor,
-            avalancheController
-        ) {
-        init {
-            mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME
-            mAutoDismissTime = TEST_AUTO_DISMISS_TIME
-        }
-
-        /** Wrapper for [BaseHeadsUpManager.shouldHeadsUpBecomePinned] for testing */
-        fun shouldHeadsUpBecomePinnedWrapper(entry: NotificationEntry): Boolean {
-            return shouldHeadsUpBecomePinned(entry)
-        }
-    }
-
-    private fun createHeadsUpManagerPhone(): HeadsUpManagerPhone {
-        return TestableHeadsUpManagerPhone(
+    private fun createHeadsUpManagerPhone(): BaseHeadsUpManager {
+        return BaseHeadsUpManager(
             mContext,
             mHeadsUpManagerLogger,
+            statusBarStateController,
+            mBypassController,
             mGroupManager,
             mVSProvider,
-            mStatusBarStateController,
-            mBypassController,
             mConfigurationController,
+            mockExecutorHandler(mExecutor),
             mGlobalSettings,
             mSystemClock,
             mExecutor,
@@ -159,20 +105,22 @@
             mUiEventLogger,
             mJavaAdapter,
             mShadeInteractor,
-            mAvalancheController
+            mAvalancheController,
         )
     }
 
     @Before
     fun setUp() {
         whenever(mShadeInteractor.isAnyExpanded).thenReturn(MutableStateFlow(false))
+        whenever(mShadeInteractor.isQsExpanded).thenReturn(MutableStateFlow(false))
+        whenever(mBypassController.bypassEnabled).thenReturn(false)
         whenever(mVSProvider.isReorderingAllowed).thenReturn(true)
         val accessibilityMgr =
             mDependency.injectMockDependency(AccessibilityManagerWrapper::class.java)
         whenever(
                 accessibilityMgr.getRecommendedTimeoutMillis(
                     ArgumentMatchers.anyInt(),
-                    ArgumentMatchers.anyInt()
+                    ArgumentMatchers.anyInt(),
                 )
             )
             .thenReturn(TEST_AUTO_DISMISS_TIME)
@@ -205,7 +153,7 @@
             hmp.removeNotification(
                 entry.key,
                 /* releaseImmediately= */ false,
-                /* reason= */ "swipe out"
+                /* reason= */ "swipe out",
             )
         Assert.assertTrue(removedImmediately)
         Assert.assertFalse(hmp.isHeadsUpEntry(entry.key))
@@ -245,6 +193,7 @@
         mSystemClock.advanceTime((TEST_AUTO_DISMISS_TIME + hmp.mExtensionTime / 2).toLong())
         Assert.assertTrue(hmp.isHeadsUpEntry(entry.key))
     }
+
     @Test
     @EnableFlags(NotificationThrottleHun.FLAG_NAME)
     fun testShowNotification_removeWhenReorderingAllowedTrue() {
@@ -253,7 +202,7 @@
 
         val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
         hmp.showNotification(notifEntry)
-        assertThat(hmp.mEntriesToRemoveWhenReorderingAllowed.contains(notifEntry)).isTrue();
+        assertThat(hmp.mEntriesToRemoveWhenReorderingAllowed.contains(notifEntry)).isTrue()
     }
 
     @Test
@@ -264,7 +213,7 @@
 
         val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
         hmp.showNotification(notifEntry)
-        assertThat(notifEntry.isSeenInShade).isTrue();
+        assertThat(notifEntry.isSeenInShade).isTrue()
     }
 
     @Test
@@ -275,197 +224,136 @@
 
         val notifEntry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
         hmp.showNotification(notifEntry)
-        assertThat(notifEntry.isSeenInShade).isFalse();
+        assertThat(notifEntry.isSeenInShade).isFalse()
     }
 
     @Test
+    fun testShouldHeadsUpBecomePinned_noFSI_false() =
+        testScope.runTest {
+            val hum = createHeadsUpManagerPhone()
+            statusBarStateController.setState(StatusBarState.KEYGUARD)
+
+            val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
+
+            Assert.assertFalse(hum.shouldHeadsUpBecomePinned(entry))
+        }
+
+    @Test
+    fun testShouldHeadsUpBecomePinned_hasFSI_notUnpinned_true() =
+        testScope.runTest {
+            val hum = createHeadsUpManagerPhone()
+            statusBarStateController.setState(StatusBarState.KEYGUARD)
+
+            val notifEntry =
+                HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id= */ 0, mContext)
+
+            // Add notifEntry to ANM mAlertEntries map and make it NOT unpinned
+            hum.showNotification(notifEntry)
+
+            val headsUpEntry = hum.getHeadsUpEntry(notifEntry.key)
+            headsUpEntry!!.mWasUnpinned = false
+
+            Assert.assertTrue(hum.shouldHeadsUpBecomePinned(notifEntry))
+        }
+
+    @Test
+    fun testShouldHeadsUpBecomePinned_wasUnpinned_false() =
+        testScope.runTest {
+            val hum = createHeadsUpManagerPhone()
+            statusBarStateController.setState(StatusBarState.KEYGUARD)
+
+            val notifEntry =
+                HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id= */ 0, mContext)
+
+            // Add notifEntry to ANM mAlertEntries map and make it unpinned
+            hum.showNotification(notifEntry)
+
+            val headsUpEntry = hum.getHeadsUpEntry(notifEntry.key)
+            headsUpEntry!!.mWasUnpinned = true
+
+            Assert.assertFalse(hum.shouldHeadsUpBecomePinned(notifEntry))
+        }
+
+    @Test
     fun shouldHeadsUpBecomePinned_shadeNotExpanded_true() =
         testScope.runTest {
             // GIVEN
-            val statusBarStateController = FakeStatusBarStateController()
             whenever(mShadeInteractor.isAnyFullyExpanded).thenReturn(MutableStateFlow(false))
-            val hmp =
-                TestableHeadsUpManagerPhone(
-                    mContext,
-                    mHeadsUpManagerLogger,
-                    mGroupManager,
-                    mVSProvider,
-                    statusBarStateController,
-                    mBypassController,
-                    mConfigurationController,
-                    mGlobalSettings,
-                    mSystemClock,
-                    mExecutor,
-                    mAccessibilityManagerWrapper,
-                    mUiEventLogger,
-                    mJavaAdapter,
-                    mShadeInteractor,
-                    mAvalancheController
-                )
+            val hmp = createHeadsUpManagerPhone()
             val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
             statusBarStateController.setState(StatusBarState.SHADE)
             runCurrent()
 
             // THEN
-            Assert.assertTrue(hmp.shouldHeadsUpBecomePinnedWrapper(entry))
+            Assert.assertTrue(hmp.shouldHeadsUpBecomePinned(entry))
         }
 
     @Test
     fun shouldHeadsUpBecomePinned_shadeLocked_false() =
         testScope.runTest {
             // GIVEN
-            val statusBarStateController = FakeStatusBarStateController()
-            val hmp =
-                TestableHeadsUpManagerPhone(
-                    mContext,
-                    mHeadsUpManagerLogger,
-                    mGroupManager,
-                    mVSProvider,
-                    statusBarStateController,
-                    mBypassController,
-                    mConfigurationController,
-                    mGlobalSettings,
-                    mSystemClock,
-                    mExecutor,
-                    mAccessibilityManagerWrapper,
-                    mUiEventLogger,
-                    mJavaAdapter,
-                    mShadeInteractor,
-                    mAvalancheController
-                )
+            val hmp = createHeadsUpManagerPhone()
             val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
             statusBarStateController.setState(StatusBarState.SHADE_LOCKED)
             runCurrent()
 
             // THEN
-            Assert.assertFalse(hmp.shouldHeadsUpBecomePinnedWrapper(entry))
+            Assert.assertFalse(hmp.shouldHeadsUpBecomePinned(entry))
         }
 
     @Test
     fun shouldHeadsUpBecomePinned_shadeUnknown_false() =
         testScope.runTest {
             // GIVEN
-            val statusBarStateController = FakeStatusBarStateController()
-            val hmp =
-                TestableHeadsUpManagerPhone(
-                    mContext,
-                    mHeadsUpManagerLogger,
-                    mGroupManager,
-                    mVSProvider,
-                    statusBarStateController,
-                    mBypassController,
-                    mConfigurationController,
-                    mGlobalSettings,
-                    mSystemClock,
-                    mExecutor,
-                    mAccessibilityManagerWrapper,
-                    mUiEventLogger,
-                    mJavaAdapter,
-                    mShadeInteractor,
-                    mAvalancheController
-                )
+            val hmp = createHeadsUpManagerPhone()
             val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
             statusBarStateController.setState(1207)
             runCurrent()
 
             // THEN
-            Assert.assertFalse(hmp.shouldHeadsUpBecomePinnedWrapper(entry))
+            Assert.assertFalse(hmp.shouldHeadsUpBecomePinned(entry))
         }
 
     @Test
     fun shouldHeadsUpBecomePinned_keyguardWithBypassOn_true() =
         testScope.runTest {
             // GIVEN
-            val statusBarStateController = FakeStatusBarStateController()
             whenever(mBypassController.bypassEnabled).thenReturn(true)
-            val hmp =
-                TestableHeadsUpManagerPhone(
-                    mContext,
-                    mHeadsUpManagerLogger,
-                    mGroupManager,
-                    mVSProvider,
-                    statusBarStateController,
-                    mBypassController,
-                    mConfigurationController,
-                    mGlobalSettings,
-                    mSystemClock,
-                    mExecutor,
-                    mAccessibilityManagerWrapper,
-                    mUiEventLogger,
-                    mJavaAdapter,
-                    mShadeInteractor,
-                    mAvalancheController
-                )
+            val hmp = createHeadsUpManagerPhone()
             val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
             statusBarStateController.setState(StatusBarState.KEYGUARD)
             runCurrent()
 
             // THEN
-            Assert.assertTrue(hmp.shouldHeadsUpBecomePinnedWrapper(entry))
+            Assert.assertTrue(hmp.shouldHeadsUpBecomePinned(entry))
         }
 
     @Test
     fun shouldHeadsUpBecomePinned_keyguardWithBypassOff_false() =
         testScope.runTest {
             // GIVEN
-            val statusBarStateController = FakeStatusBarStateController()
             whenever(mBypassController.bypassEnabled).thenReturn(false)
-            val hmp =
-                TestableHeadsUpManagerPhone(
-                    mContext,
-                    mHeadsUpManagerLogger,
-                    mGroupManager,
-                    mVSProvider,
-                    statusBarStateController,
-                    mBypassController,
-                    mConfigurationController,
-                    mGlobalSettings,
-                    mSystemClock,
-                    mExecutor,
-                    mAccessibilityManagerWrapper,
-                    mUiEventLogger,
-                    mJavaAdapter,
-                    mShadeInteractor,
-                    mAvalancheController
-                )
+            val hmp = createHeadsUpManagerPhone()
             val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
             statusBarStateController.setState(StatusBarState.KEYGUARD)
             runCurrent()
 
             // THEN
-            Assert.assertFalse(hmp.shouldHeadsUpBecomePinnedWrapper(entry))
+            Assert.assertFalse(hmp.shouldHeadsUpBecomePinned(entry))
         }
 
     @Test
     fun shouldHeadsUpBecomePinned_shadeExpanded_false() =
         testScope.runTest {
             // GIVEN
-            val statusBarStateController = FakeStatusBarStateController()
             whenever(mShadeInteractor.isAnyExpanded).thenReturn(MutableStateFlow(true))
-            val hmp =
-                TestableHeadsUpManagerPhone(
-                    mContext,
-                    mHeadsUpManagerLogger,
-                    mGroupManager,
-                    mVSProvider,
-                    statusBarStateController,
-                    mBypassController,
-                    mConfigurationController,
-                    mGlobalSettings,
-                    mSystemClock,
-                    mExecutor,
-                    mAccessibilityManagerWrapper,
-                    mUiEventLogger,
-                    mJavaAdapter,
-                    mShadeInteractor,
-                    mAvalancheController
-                )
+            val hmp = createHeadsUpManagerPhone()
             val entry = HeadsUpManagerTestUtil.createEntry(/* id= */ 0, mContext)
             statusBarStateController.setState(StatusBarState.SHADE)
             runCurrent()
 
             // THEN
-            Assert.assertFalse(hmp.shouldHeadsUpBecomePinnedWrapper(entry))
+            Assert.assertFalse(hmp.shouldHeadsUpBecomePinned(entry))
         }
 
     companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java
index 3f33d2f..8593f6a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java
@@ -21,9 +21,9 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.view.RotationPolicy;
@@ -37,7 +37,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper
 @SmallTest
 public class RotationLockControllerImplTest extends SysuiTestCase {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
index 3efabd7..59987f4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.policy;
 
-import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
 import static com.android.systemui.util.concurrency.MockExecutorHandlerKt.mockExecutorHandler;
 
 import static org.mockito.Mockito.spy;
@@ -28,8 +27,14 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.settings.GlobalSettings;
 import com.android.systemui.util.time.SystemClock;
 
@@ -37,16 +42,39 @@
 
     private HeadsUpEntry mLastCreatedEntry;
 
-    TestableHeadsUpManager(Context context,
+    TestableHeadsUpManager(
+            Context context,
             HeadsUpManagerLogger logger,
+            StatusBarStateController statusBarStateController,
+            KeyguardBypassController bypassController,
+            GroupMembershipManager groupMembershipManager,
+            VisualStabilityProvider visualStabilityProvider,
+            ConfigurationController configurationController,
             DelayableExecutor executor,
             GlobalSettings globalSettings,
             SystemClock systemClock,
             AccessibilityManagerWrapper accessibilityManagerWrapper,
             UiEventLogger uiEventLogger,
+            JavaAdapter javaAdapter,
+            ShadeInteractor shadeInteractor,
             AvalancheController avalancheController) {
-        super(context, logger, mockExecutorHandler(executor), globalSettings, systemClock,
-                executor, accessibilityManagerWrapper, uiEventLogger, avalancheController);
+        super(
+                context,
+                logger,
+                statusBarStateController,
+                bypassController,
+                groupMembershipManager,
+                visualStabilityProvider,
+                configurationController,
+                mockExecutorHandler(executor),
+                globalSettings,
+                systemClock,
+                executor,
+                accessibilityManagerWrapper,
+                uiEventLogger,
+                javaAdapter,
+                shadeInteractor,
+                avalancheController);
 
         mTouchAcceptanceDelay = BaseHeadsUpManagerTest.TEST_TOUCH_ACCEPTANCE_TIME;
         mMinimumDisplayTime = BaseHeadsUpManagerTest.TEST_MINIMUM_DISPLAY_TIME;
@@ -61,11 +89,6 @@
         return mLastCreatedEntry;
     }
 
-    @Override
-    public int getContentFlag() {
-        return FLAG_CONTENT_VIEW_CONTRACTED;
-    }
-
     // The following are only implemented by HeadsUpManagerPhone. If you need them, use that.
     @Override
     public void addHeadsUpPhoneListener(@NonNull OnHeadsUpPhoneListenerChange listener) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt
index 9592b28..798380a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt
@@ -18,8 +18,8 @@
 
 import android.hardware.BatteryState
 import android.hardware.input.InputManager
-import android.testing.AndroidTestingRunner
 import android.view.InputDevice
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FeatureFlags
@@ -35,7 +35,7 @@
 import org.mockito.Mockito.verifyNoMoreInteractions
 import org.mockito.MockitoAnnotations
 
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @SmallTest
 class StylusUsiPowerStartableTest : SysuiTestCase() {
     @Mock lateinit var inputManager: InputManager
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/TestableAlertDialogTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/TestableAlertDialogTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/util/TestableAlertDialogTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/TestableAlertDialogTest.kt
index 01dd60a..4351d28 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/TestableAlertDialogTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/TestableAlertDialogTest.kt
@@ -21,8 +21,8 @@
 import android.content.DialogInterface.BUTTON_NEGATIVE
 import android.content.DialogInterface.BUTTON_NEUTRAL
 import android.content.DialogInterface.BUTTON_POSITIVE
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.mockito.any
@@ -36,7 +36,7 @@
 import org.mockito.Mockito.verify
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
 class TestableAlertDialogTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/KeepAwakeAnimationListenerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/KeepAwakeAnimationListenerTest.java
similarity index 96%
rename from packages/SystemUI/tests/src/com/android/systemui/util/wakelock/KeepAwakeAnimationListenerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/KeepAwakeAnimationListenerTest.java
index 1ff9548..c6bfb35 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/KeepAwakeAnimationListenerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/KeepAwakeAnimationListenerTest.java
@@ -21,9 +21,9 @@
 import static org.mockito.Mockito.verify;
 
 import android.animation.Animator;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -36,7 +36,7 @@
 
 @SmallTest
 @TestableLooper.RunWithLooper
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class KeepAwakeAnimationListenerTest extends SysuiTestCase {
     @Mock WakeLock mWakeLock;
     KeepAwakeAnimationListener mKeepAwakeAnimationListener;
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
index 917a4ff..ccd953d 100644
--- a/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
+++ b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
@@ -24,6 +24,7 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CornerSize
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
@@ -31,7 +32,6 @@
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableIntStateOf
-import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
@@ -61,6 +61,7 @@
 import com.android.systemui.haptics.slider.SliderHapticFeedbackConfig
 import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel
 import com.android.systemui.lifecycle.rememberViewModel
+import com.android.systemui.qs.ui.compose.borderOnFocus
 import com.android.systemui.res.R
 import com.android.systemui.utils.PolicyRestriction
 
@@ -102,11 +103,12 @@
             null
         }
 
-    val overriddenByAppState by if (Flags.showToastWhenAppControlBrightness()) {
-        viewModel.brightnessOverriddenByWindow.collectAsStateWithLifecycle()
-    } else {
-        mutableStateOf(false)
-    }
+    val overriddenByAppState =
+        if (Flags.showToastWhenAppControlBrightness()) {
+            viewModel.brightnessOverriddenByWindow.collectAsStateWithLifecycle().value
+        } else {
+            false
+        }
 
     PlatformSlider(
         value = animatedValue,
@@ -160,7 +162,7 @@
                 if (interaction is DragInteraction.Start && overriddenByAppState) {
                     viewModel.showToast(
                         context,
-                        R.string.quick_settings_brightness_unable_adjust_msg
+                        R.string.quick_settings_brightness_unable_adjust_msg,
                     )
                 }
             }
@@ -213,7 +215,11 @@
                 coroutineScope.launch { viewModel.onDrag(Drag.Stopped(GammaBrightness(it))) }
             },
             modifier =
-                Modifier.then(if (viewModel.showMirror) Modifier.drawInOverlay() else Modifier)
+                Modifier.borderOnFocus(
+                        color = MaterialTheme.colorScheme.secondary,
+                        cornerSize = CornerSize(32.dp),
+                    )
+                    .then(if (viewModel.showMirror) Modifier.drawInOverlay() else Modifier)
                     .sliderBackground(containerColor)
                     .fillMaxWidth(),
             formatter = viewModel::formatValue,
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
index b74ca03..35eed5e 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
@@ -35,6 +35,7 @@
 import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository
 import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
 import javax.inject.Inject
@@ -363,6 +364,9 @@
         private val interactor: DeviceUnlockedInteractor,
     ) : CoreStartable {
         override fun start() {
+            if (!SceneContainerFlag.isEnabled)
+                return
+
             applicationScope.launch { interactor.activate() }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepository.kt
index 80eb9ee..f310b30 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepository.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.display.shared.model.DisplayWindowProperties
 import com.android.systemui.res.R
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
 import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
 import com.google.common.collect.HashBasedTable
 import com.google.common.collect.Table
@@ -60,7 +61,10 @@
 ) : DisplayWindowPropertiesRepository, CoreStartable {
 
     init {
-        StatusBarConnectedDisplays.assertInNewMode()
+        check(StatusBarConnectedDisplays.isEnabled || ShadeWindowGoesAround.isEnabled) {
+            "This should be instantiated only when wither StatusBarConnectedDisplays or " +
+                "ShadeWindowGoesAround are enabled."
+        }
     }
 
     private val properties: Table<Int, Int, DisplayWindowProperties> = HashBasedTable.create()
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayStore.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayStore.kt
index ecddef6..711534f 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayStore.kt
@@ -17,13 +17,12 @@
 package com.android.systemui.display.data.repository
 
 import android.view.Display
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.qualifiers.Background
 import java.io.PrintWriter
 import java.util.concurrent.ConcurrentHashMap
-import kotlinx.coroutines.CoroutineName
 import kotlinx.coroutines.CoroutineScope
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /** Provides per display instances of [T]. */
 interface PerDisplayStore<T> {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModel.kt
index 8b6cc8c..b8ac0d2 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModel.kt
@@ -16,17 +16,64 @@
 
 package com.android.systemui.dreams.ui.viewmodel
 
+import com.android.compose.animation.scene.Swipe
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
+import com.android.systemui.scene.shared.model.SceneFamilies
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.shade.ui.viewmodel.dualShadeActions
+import com.android.systemui.shade.ui.viewmodel.singleShadeActions
+import com.android.systemui.shade.ui.viewmodel.splitShadeActions
+import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
 
 /** Handles user input for the dream scene. */
-class DreamUserActionsViewModel @AssistedInject constructor() : UserActionsViewModel() {
+class DreamUserActionsViewModel
+@AssistedInject
+constructor(
+    private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
+    private val shadeInteractor: ShadeInteractor,
+) : UserActionsViewModel() {
 
     override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
-        setActions(emptyMap())
+        shadeInteractor.isShadeTouchable
+            .flatMapLatestConflated { isShadeTouchable ->
+                if (!isShadeTouchable) {
+                    flowOf(emptyMap())
+                } else {
+                    combine(
+                        deviceUnlockedInteractor.deviceUnlockStatus.map { it.isUnlocked },
+                        shadeInteractor.shadeMode,
+                    ) { isDeviceUnlocked, shadeMode ->
+                        buildList {
+                                val bouncerOrGone =
+                                    if (isDeviceUnlocked) Scenes.Gone else Scenes.Bouncer
+                                add(Swipe.Up to bouncerOrGone)
+
+                                // "Home" is either Dream, Lockscreen, or Gone.
+                                add(Swipe.End to SceneFamilies.Home)
+
+                                addAll(
+                                    when (shadeMode) {
+                                        ShadeMode.Single -> singleShadeActions()
+                                        ShadeMode.Split -> splitShadeActions()
+                                        ShadeMode.Dual -> dualShadeActions()
+                                    }
+                                )
+                            }
+                            .associate { it }
+                    }
+                }
+            }
+            .collect { setActions(it) }
     }
 
     @AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModel.kt b/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModel.kt
index ed7d182..316964a 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModel.kt
@@ -83,9 +83,6 @@
                     interactionState == TileInteractionState.LONG_CLICKED &&
                         animationState == TileAnimationState.ACTIVITY_LAUNCH ->
                         TileHapticsState.LONG_PRESS
-                    interactionState == TileInteractionState.LONG_CLICKED &&
-                        !tileViewModel.currentState.handlesLongClick ->
-                        TileHapticsState.FAILED_LONGPRESS
                     else -> TileHapticsState.NO_HAPTICS
                 }
             }
@@ -102,7 +99,6 @@
                         TileHapticsState.TOGGLE_ON -> MSDLToken.SWITCH_ON
                         TileHapticsState.TOGGLE_OFF -> MSDLToken.SWITCH_OFF
                         TileHapticsState.LONG_PRESS -> MSDLToken.LONG_PRESS
-                        TileHapticsState.FAILED_LONGPRESS -> MSDLToken.FAILURE
                         TileHapticsState.NO_HAPTICS -> null
                     }
                 tokenToPlay?.let {
@@ -154,7 +150,6 @@
         TOGGLE_ON,
         TOGGLE_OFF,
         LONG_PRESS,
-        FAILED_LONGPRESS,
         NO_HAPTICS,
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt
index 7f8fbb5..ec1d358 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt
@@ -49,6 +49,7 @@
     @Background private val backgroundScope: CoroutineScope,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val shortcutCategoriesUtils: ShortcutCategoriesUtils,
+    private val context: Context,
 ) : ShortcutCategoriesRepository {
 
     private val userContext: Context
@@ -147,25 +148,23 @@
     private fun fetchGroupLabelByGestureType(
         @KeyGestureEvent.KeyGestureType keyGestureType: Int
     ): String? {
-        return InputGestures.gestureToInternalKeyboardShortcutGroupLabelMap.getOrDefault(
-            keyGestureType,
-            null,
-        )
+        InputGestures.gestureToInternalKeyboardShortcutGroupLabelMap[keyGestureType]?.let {
+            return context.getString(it)
+        } ?: return null
     }
 
     private fun fetchShortcutInfoLabelByGestureType(
         @KeyGestureEvent.KeyGestureType keyGestureType: Int
     ): String? {
-        return InputGestures.gestureToInternalKeyboardShortcutInfoLabelMap.getOrDefault(
-            keyGestureType,
-            null,
-        )
+        InputGestures.gestureToInternalKeyboardShortcutInfoLabelMap[keyGestureType]?.let {
+            return context.getString(it)
+        } ?: return null
     }
 
     private fun fetchShortcutCategoryTypeByGestureType(
         @KeyGestureEvent.KeyGestureType keyGestureType: Int
     ): ShortcutCategoryType? {
-        return InputGestures.gestureToShortcutCategoryTypeMap.getOrDefault(keyGestureType, null)
+        return InputGestures.gestureToShortcutCategoryTypeMap[keyGestureType]
     }
 
     private data class InternalGroupsSource(
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestures.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestures.kt
index 28134db..90be988 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestures.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestures.kt
@@ -44,6 +44,7 @@
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.AppCategories
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System
+import com.android.systemui.res.R
 
 object InputGestures {
     val gestureToShortcutCategoryTypeMap =
@@ -80,77 +81,97 @@
             KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING to AppCategories,
         )
 
-    // TODO move all string to to resources use the same resources as the original shortcuts
-    // - that way when the strings are translated there are no discrepancies
     val gestureToInternalKeyboardShortcutGroupLabelMap =
         mapOf(
             // System Category
-            KEY_GESTURE_TYPE_HOME to "System controls",
-            KEY_GESTURE_TYPE_RECENT_APPS to "System controls",
-            KEY_GESTURE_TYPE_BACK to "System controls",
-            KEY_GESTURE_TYPE_TAKE_SCREENSHOT to "System controls",
-            KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER to "System controls",
-            KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL to "System controls",
-            KEY_GESTURE_TYPE_LOCK_SCREEN to "System controls",
-            KEY_GESTURE_TYPE_ALL_APPS to "System controls",
-            KEY_GESTURE_TYPE_OPEN_NOTES to "System apps",
-            KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS to "System apps",
-            KEY_GESTURE_TYPE_LAUNCH_ASSISTANT to "System apps",
-            KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT to "System apps",
+            KEY_GESTURE_TYPE_HOME to R.string.shortcut_helper_category_system_controls,
+            KEY_GESTURE_TYPE_RECENT_APPS to R.string.shortcut_helper_category_system_controls,
+            KEY_GESTURE_TYPE_BACK to R.string.shortcut_helper_category_system_controls,
+            KEY_GESTURE_TYPE_TAKE_SCREENSHOT to R.string.shortcut_helper_category_system_controls,
+            KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER to
+                R.string.shortcut_helper_category_system_controls,
+            KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL to
+                R.string.shortcut_helper_category_system_controls,
+            KEY_GESTURE_TYPE_LOCK_SCREEN to R.string.shortcut_helper_category_system_controls,
+            KEY_GESTURE_TYPE_ALL_APPS to R.string.shortcut_helper_category_system_controls,
+            KEY_GESTURE_TYPE_OPEN_NOTES to R.string.shortcut_helper_category_system_apps,
+            KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS to
+                R.string.shortcut_helper_category_system_apps,
+            KEY_GESTURE_TYPE_LAUNCH_ASSISTANT to R.string.shortcut_helper_category_system_apps,
+            KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT to
+                R.string.shortcut_helper_category_system_apps,
 
             // Multitasking Category
-            KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER to "Recent apps",
-            KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT to "Split screen",
-            KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT to "Split screen",
-            KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION to "Split screen",
-            KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT to "Split screen",
-            KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT to "Split screen",
+            KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER to R.string.shortcutHelper_category_recent_apps,
+            KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT to
+                R.string.shortcutHelper_category_split_screen,
+            KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT to
+                R.string.shortcutHelper_category_split_screen,
+            KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION to
+                R.string.shortcutHelper_category_split_screen,
+            KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT to
+                R.string.shortcutHelper_category_split_screen,
+            KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT to
+                R.string.shortcutHelper_category_split_screen,
 
             // App Category
-            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR to "Applications",
-            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR to "Applications",
-            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER to "Applications",
-            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS to "Applications",
-            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL to "Applications",
-            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS to "Applications",
-            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING to "Applications",
+            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR to
+                R.string.keyboard_shortcut_group_applications,
+            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR to
+                R.string.keyboard_shortcut_group_applications,
+            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER to
+                R.string.keyboard_shortcut_group_applications,
+            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS to
+                R.string.keyboard_shortcut_group_applications,
+            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL to R.string.keyboard_shortcut_group_applications,
+            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS to R.string.keyboard_shortcut_group_applications,
+            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING to
+                R.string.keyboard_shortcut_group_applications,
         )
 
     val gestureToInternalKeyboardShortcutInfoLabelMap =
         mapOf(
             // System Category
-            KEY_GESTURE_TYPE_HOME to "Go to home screen",
-            KEY_GESTURE_TYPE_RECENT_APPS to "View recent apps",
-            KEY_GESTURE_TYPE_BACK to "Go back",
-            KEY_GESTURE_TYPE_TAKE_SCREENSHOT to "Take screenshot",
-            KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER to "Show shortcuts",
-            KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL to "View notifications",
-            KEY_GESTURE_TYPE_LOCK_SCREEN to "Lock screen",
-            KEY_GESTURE_TYPE_ALL_APPS to "Open apps list",
-            KEY_GESTURE_TYPE_OPEN_NOTES to "Take a note",
-            KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS to "Open settings",
-            KEY_GESTURE_TYPE_LAUNCH_ASSISTANT to "Open assistant",
-            KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT to "Open assistant",
+            KEY_GESTURE_TYPE_HOME to R.string.group_system_access_home_screen,
+            KEY_GESTURE_TYPE_RECENT_APPS to R.string.group_system_overview_open_apps,
+            KEY_GESTURE_TYPE_BACK to R.string.group_system_go_back,
+            KEY_GESTURE_TYPE_TAKE_SCREENSHOT to R.string.group_system_full_screenshot,
+            KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER to
+                R.string.group_system_access_system_app_shortcuts,
+            KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL to
+                R.string.group_system_access_notification_shade,
+            KEY_GESTURE_TYPE_LOCK_SCREEN to R.string.group_system_lock_screen,
+            KEY_GESTURE_TYPE_ALL_APPS to R.string.group_system_access_all_apps_search,
+            KEY_GESTURE_TYPE_OPEN_NOTES to R.string.group_system_quick_memo,
+            KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS to R.string.group_system_access_system_settings,
+            KEY_GESTURE_TYPE_LAUNCH_ASSISTANT to R.string.group_system_access_google_assistant,
+            KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT to
+                R.string.group_system_access_google_assistant,
 
             // Multitasking Category
-            KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER to "Cycle forward through recent apps",
-            KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT to
-                "Use split screen with current app on the left",
-            KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT to
-                "Use split screen with current app on the right",
-            KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION to "Switch from split screen to full screen",
+            KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER to R.string.group_system_cycle_forward,
+            KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT to R.string.system_multitasking_lhs,
+            KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT to R.string.system_multitasking_rhs,
+            KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION to R.string.system_multitasking_full_screen,
             KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT to
-                "Switch to app on left or above while using split screen",
+                R.string.system_multitasking_splitscreen_focus_lhs,
             KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT to
-                "Switch to app on right or below while using split screen",
+                R.string.system_multitasking_splitscreen_focus_rhs,
 
             // App Category
-            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR to "Calculator",
-            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR to "Calendar",
-            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER to "Chrome",
-            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS to "Contacts",
-            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL to "Gmail",
-            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS to "Maps",
-            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING to "Messages",
+            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR to
+                R.string.keyboard_shortcut_group_applications_calculator,
+            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR to
+                R.string.keyboard_shortcut_group_applications_calendar,
+            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER to
+                R.string.keyboard_shortcut_group_applications_browser,
+            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS to
+                R.string.keyboard_shortcut_group_applications_contacts,
+            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL to
+                R.string.keyboard_shortcut_group_applications_email,
+            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS to
+                R.string.keyboard_shortcut_group_applications_maps,
+            KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING to
+                R.string.keyboard_shortcut_group_applications_sms,
         )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
index e761c73..58ce194 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
@@ -18,6 +18,7 @@
 
 import androidx.compose.foundation.BorderStroke
 import androidx.compose.foundation.IndicationNodeFactory
+import androidx.compose.foundation.LocalIndication
 import androidx.compose.foundation.background
 import androidx.compose.foundation.border
 import androidx.compose.foundation.clickable
@@ -126,8 +127,7 @@
                     .selectable(
                         selected = selected,
                         interactionSource = interactionSource,
-                        indication =
-                            ShortcutHelperIndication(interactionSource, interactionsConfig),
+                        indication = ShortcutHelperIndication(interactionsConfig),
                         enabled = enabled,
                         onClick = onClick,
                     )
@@ -181,8 +181,7 @@
                     )
                     .clickable(
                         interactionSource = interactionSource,
-                        indication =
-                            ShortcutHelperIndication(interactionSource, interactionsConfig),
+                        indication = ShortcutHelperIndication(interactionsConfig),
                         enabled = enabled,
                         onClick = onClick,
                     ),
@@ -507,10 +506,8 @@
     }
 }
 
-data class ShortcutHelperIndication(
-    private val interactionSource: InteractionSource,
-    private val interactionsConfig: InteractionsConfig,
-) : IndicationNodeFactory {
+data class ShortcutHelperIndication(private val interactionsConfig: InteractionsConfig) :
+    IndicationNodeFactory {
     override fun create(interactionSource: InteractionSource): DelegatableNode {
         return ShortcutHelperInteractionsNode(interactionSource, interactionsConfig)
     }
@@ -529,3 +526,15 @@
     val hoverPadding: Dp = 0.dp,
     val pressedPadding: Dp = hoverPadding,
 )
+
+@Composable
+fun ProvideShortcutHelperIndication(
+    interactionsConfig: InteractionsConfig,
+    content: @Composable () -> Unit,
+) {
+    CompositionLocalProvider(
+        LocalIndication provides ShortcutHelperIndication(interactionsConfig)
+    ) {
+        content()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 2d05600..5ec6d37 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -95,7 +95,7 @@
     private val keyguardBlueprintViewModel: KeyguardBlueprintViewModel,
     private val keyguardStatusViewComponentFactory: KeyguardStatusViewComponent.Factory,
     @ShadeDisplayAware private val configuration: ConfigurationState,
-    private val context: Context,
+    @ShadeDisplayAware private val context: Context,
     private val keyguardIndicationController: KeyguardIndicationController,
     private val shadeInteractor: ShadeInteractor,
     private val interactionJankMonitor: InteractionJankMonitor,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java b/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java
index 39144b5..c0ffda6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java
@@ -34,6 +34,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.res.R;
+import com.android.systemui.shade.ShadeDisplayAware;
 import com.android.systemui.util.time.SystemClock;
 
 import java.io.PrintWriter;
@@ -91,7 +92,7 @@
 
     @Inject
     public WakefulnessLifecycle(
-            Context context,
+            @ShadeDisplayAware Context context,
             @Nullable IWallpaperManager wallpaperManagerService,
             SystemClock systemClock,
             DumpManager dumpManager) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerOcclusionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerOcclusionManager.kt
index 585bd6a..4bac8f7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerOcclusionManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerOcclusionManager.kt
@@ -46,6 +46,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import java.util.concurrent.Executor
 import javax.inject.Inject
 
@@ -83,7 +84,7 @@
     val activityTransitionAnimator: ActivityTransitionAnimator,
     val keyguardViewController: dagger.Lazy<KeyguardViewController>,
     val powerInteractor: PowerInteractor,
-    val context: Context,
+    @ShadeDisplayAware val context: Context,
     val interactionJankMonitor: InteractionJankMonitor,
     @Main executor: Executor,
     val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 7638079..0101e09 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -68,6 +68,7 @@
 import com.android.systemui.process.ProcessWrapper;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeDisplayAware;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -126,7 +127,7 @@
     @Provides
     @SysUISingleton
     static KeyguardViewMediator newKeyguardViewMediator(
-            Context context,
+            @ShadeDisplayAware Context context,
             UiEventLogger uiEventLogger,
             SessionTracker sessionTracker,
             UserTracker userTracker,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
index 77e8179..74ee052 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
@@ -27,9 +27,9 @@
 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.dagger.qualifiers.Background
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.ShadeDisplayAware
 import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
@@ -41,7 +41,7 @@
 class CameraQuickAffordanceConfig
 @Inject
 constructor(
-    @Application private val context: Context,
+    @ShadeDisplayAware private val context: Context,
     private val packageManager: PackageManager,
     private val cameraGestureHelper: Lazy<CameraGestureHelper>,
     private val userTracker: UserTracker,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
index be87334..d1f9fa2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfig.kt
@@ -40,6 +40,7 @@
 import com.android.systemui.modes.shared.ModesUi
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.policy.ZenModeController
 import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
 import com.android.systemui.util.settings.SecureSettings
@@ -75,7 +76,7 @@
 
     @Inject
     constructor(
-        context: Context,
+        @ShadeDisplayAware context: Context,
         controller: ZenModeController,
         interactor: ZenModeInteractor,
         secureSettings: SecureSettings,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt
index a7999c1..480ef5e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfig.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.policy.FlashlightController
 import javax.inject.Inject
 import kotlinx.coroutines.channels.awaitClose
@@ -36,7 +37,7 @@
 class FlashlightQuickAffordanceConfig
 @Inject
 constructor(
-    @Application private val context: Context,
+    @ShadeDisplayAware private val context: Context,
     private val flashlightController: FlashlightController,
 ) : KeyguardQuickAffordanceConfig {
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
index cc36961..3555f06 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
@@ -36,6 +36,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.Companion.appStoreIntent
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.util.kotlin.getOrNull
 import javax.inject.Inject
 import kotlinx.coroutines.channels.awaitClose
@@ -48,7 +49,7 @@
 class HomeControlsKeyguardQuickAffordanceConfig
 @Inject
 constructor(
-    @Application private val context: Context,
+    @ShadeDisplayAware private val context: Context,
     private val component: ControlsComponent,
 ) : KeyguardQuickAffordanceConfig {
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt
index 796374a..f08576a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.channels.awaitClose
@@ -47,7 +48,7 @@
 class KeyguardQuickAffordanceLocalUserSelectionManager
 @Inject
 constructor(
-    @Application private val context: Context,
+    @ShadeDisplayAware private val context: Context,
     private val userFileManager: UserFileManager,
     private val userTracker: UserTracker,
     broadcastDispatcher: BroadcastDispatcher,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt
index 6c1bdad..1358634 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.util.RingerModeTracker
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
@@ -51,7 +52,7 @@
 class MuteQuickAffordanceConfig
 @Inject
 constructor(
-    private val context: Context,
+    @ShadeDisplayAware private val context: Context,
     private val userTracker: UserTracker,
     private val userFileManager: UserFileManager,
     private val ringerModeTracker: RingerModeTracker,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
index a503541..d12c42a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
@@ -36,7 +37,7 @@
 class QrCodeScannerKeyguardQuickAffordanceConfig
 @Inject
 constructor(
-    @Application private val context: Context,
+    @ShadeDisplayAware private val context: Context,
     private val controller: QRCodeScannerController,
 ) : KeyguardQuickAffordanceConfig {
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index 56b520e..eafa1ce 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.wallet.controller.QuickAccessWalletController
 import com.android.systemui.wallet.util.getPaymentCards
 import javax.inject.Inject
@@ -51,7 +52,7 @@
 class QuickAccessWalletKeyguardQuickAffordanceConfig
 @Inject
 constructor(
-    @Application private val context: Context,
+    @ShadeDisplayAware private val context: Context,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val walletController: QuickAccessWalletController,
     private val activityStarter: ActivityStarter,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt
index 3e6e3b7..ceaeeca 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/VideoCameraQuickAffordanceConfig.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.Flow
@@ -42,7 +43,7 @@
 class VideoCameraQuickAffordanceConfig
 @Inject
 constructor(
-    @Application private val context: Context,
+    @ShadeDisplayAware private val context: Context,
     private val cameraIntents: CameraIntentsWrapper,
     private val activityIntentHelper: ActivityIntentHelper,
     private val userTracker: UserTracker,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
index dd3e619..ab8cc71 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
@@ -40,6 +40,7 @@
 import com.android.systemui.keyguard.shared.model.AuthenticationFlags
 import com.android.systemui.keyguard.shared.model.DevicePosture
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
 import com.android.systemui.user.data.repository.UserRepository
 import java.io.PrintWriter
@@ -123,7 +124,7 @@
 class BiometricSettingsRepositoryImpl
 @Inject
 constructor(
-    context: Context,
+    @ShadeDisplayAware context: Context,
     lockPatternUtils: LockPatternUtils,
     broadcastDispatcher: BroadcastDispatcher,
     authController: AuthController,
@@ -354,7 +355,10 @@
 }
 
 @OptIn(ExperimentalCoroutinesApi::class)
-private class StrongAuthTracker(private val userRepository: UserRepository, context: Context?) :
+private class StrongAuthTracker(
+    private val userRepository: UserRepository,
+    @ShadeDisplayAware context: Context?
+) :
     LockPatternUtils.StrongAuthTracker(context) {
 
     private val selectedUserId =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
index 95d1b5d..283651d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.plugins.clocks.ClockId
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.shared.clocks.ClockRegistry
 import com.android.systemui.util.settings.SecureSettings
 import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
@@ -89,7 +90,7 @@
     override val clockEventController: ClockEventController,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
     @Application private val applicationScope: CoroutineScope,
-    @Application private val applicationContext: Context,
+    @ShadeDisplayAware private val context: Context,
     private val featureFlags: FeatureFlagsClassic,
 ) : KeyguardClockRepository {
 
@@ -166,7 +167,7 @@
         get() =
             featureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE) &&
                 // True on small landscape screens
-                applicationContext.resources.getBoolean(R.bool.force_small_clock_on_lockscreen)
+                context.resources.getBoolean(R.bool.force_small_clock_on_lockscreen)
 
     private fun getClockSize(): ClockSizeSetting {
         return ClockSizeSetting.fromSettingValue(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
index d0de21b..c1ec88b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
@@ -36,6 +36,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.util.kotlin.FlowDumperImpl
 import java.io.PrintWriter
 import javax.inject.Inject
@@ -57,7 +58,7 @@
 class KeyguardQuickAffordanceRepository
 @Inject
 constructor(
-    @Application private val appContext: Context,
+    @ShadeDisplayAware private val appContext: Context,
     @Application private val scope: CoroutineScope,
     private val localUserSelectionManager: KeyguardQuickAffordanceLocalUserSelectionManager,
     private val remoteUserSelectionManager: KeyguardQuickAffordanceRemoteUserSelectionManager,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepository.kt
index b67fd4b..549a508 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepository.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.keyguard.data.repository
 
-import android.content.Context
 import android.os.UserHandle
 import android.provider.Settings
 import android.view.View
@@ -46,7 +45,6 @@
 class KeyguardSmartspaceRepositoryImpl
 @Inject
 constructor(
-    context: Context,
     private val secureSettings: SecureSettings,
     private val userTracker: UserTracker,
     @Application private val applicationScope: CoroutineScope,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
index 4c9c282..4f6319a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.power.shared.model.WakeSleepReason
 import com.android.systemui.power.shared.model.WakeSleepReason.TAP
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.CircleReveal
 import com.android.systemui.statusbar.LiftReveal
 import com.android.systemui.statusbar.LightRevealEffect
@@ -77,7 +78,7 @@
 @Inject
 constructor(
     keyguardRepository: KeyguardRepository,
-    val context: Context,
+    @ShadeDisplayAware val context: Context,
     powerRepository: PowerRepository,
     private val scrimLogger: ScrimLogger,
 ) : LightRevealScrimRepository {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
index 73a4cc3..68ec4f3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
@@ -45,7 +45,7 @@
 class BurnInInteractor
 @Inject
 constructor(
-    private val context: Context,
+    @ShadeDisplayAware private val context: Context,
     private val burnInHelperWrapper: BurnInHelperWrapper,
     @Application private val scope: CoroutineScope,
     @ShadeDisplayAware private val configurationInteractor: ConfigurationInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
index 03cf1a4..6367b5c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
@@ -47,7 +48,7 @@
 class DeviceEntrySideFpsOverlayInteractor
 @Inject
 constructor(
-    @Application private val context: Context,
+    @ShadeDisplayAware private val context: Context,
     deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
     private val sceneInteractor: SceneInteractor,
     private val primaryBouncerInteractor: PrimaryBouncerInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
index d4d7e75..2d81be6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.shade.ShadeController
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import javax.inject.Inject
@@ -39,7 +40,7 @@
 class KeyguardKeyEventInteractor
 @Inject
 constructor(
-    private val context: Context,
+    @ShadeDisplayAware private val context: Context,
     private val statusBarStateController: StatusBarStateController,
     private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
     private val shadeController: ShadeController,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 8c9473f..ae55825 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -51,6 +51,7 @@
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
 import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.KEYGUARD_QUICK_AFFORDANCE_ID_NONE
@@ -90,7 +91,7 @@
     private val dockManager: DockManager,
     private val biometricSettingsRepository: BiometricSettingsRepository,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
-    @Application private val appContext: Context,
+    @ShadeDisplayAware private val appContext: Context,
     private val sceneInteractor: Lazy<SceneInteractor>,
 ) {
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
index 377a03e..b9784f1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
 import com.android.systemui.util.kotlin.sample
 import com.android.systemui.util.kotlin.toPx
@@ -45,7 +46,7 @@
 @Inject
 constructor(
     private val repository: KeyguardSurfaceBehindRepository,
-    context: Context,
+    @ShadeDisplayAware context: Context,
     transitionInteractor: KeyguardTransitionInteractor,
     inWindowLauncherUnlockAnimationInteractor: Lazy<InWindowLauncherUnlockAnimationInteractor>,
     swipeToDismissInteractor: SwipeToDismissInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt
index b2031d3..274a1dd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.res.R
 import com.android.systemui.shade.PulsingGestureListener
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -58,7 +59,7 @@
 class KeyguardTouchHandlingInteractor
 @Inject
 constructor(
-    @Application private val appContext: Context,
+    @ShadeDisplayAware private val context: Context,
     @Application private val scope: CoroutineScope,
     transitionInteractor: KeyguardTransitionInteractor,
     repository: KeyguardRepository,
@@ -188,7 +189,7 @@
 
     private fun isFeatureEnabled(): Boolean {
         return featureFlags.isEnabled(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED) &&
-            appContext.resources.getBoolean(R.bool.long_press_keyguard_customize_lockscreen_enabled)
+            context.resources.getBoolean(R.bool.long_press_keyguard_customize_lockscreen_enabled)
     }
 
     /** Updates application state to ask to show the menu. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt
index 9c98a96..fbc7e2a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt
@@ -38,6 +38,7 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.power.shared.model.WakeSleepReason
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.kotlin.sample
 import com.android.systemui.util.settings.SecureSettings
@@ -75,7 +76,7 @@
 @Inject
 constructor(
     @Application private val scope: CoroutineScope,
-    private val context: Context,
+    @ShadeDisplayAware private val context: Context,
     private val repository: KeyguardRepository,
     private val systemClock: SystemClock,
     private val alarmManager: AlarmManager,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NaturalScrollingSettingObserver.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NaturalScrollingSettingObserver.kt
index 508fb59..7e77423 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NaturalScrollingSettingObserver.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NaturalScrollingSettingObserver.kt
@@ -24,6 +24,7 @@
 import android.provider.Settings.SettingNotFoundException
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 
 @SysUISingleton
@@ -31,7 +32,7 @@
 @Inject
 constructor(
     @Main private val handler: Handler,
-    private val context: Context,
+    @ShadeDisplayAware private val context: Context,
 ) {
     var isNaturalScrollingEnabled = true
         get() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StatusBarDisableFlagsInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StatusBarDisableFlagsInteractor.kt
index 73e80ff..d6a110a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StatusBarDisableFlagsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StatusBarDisableFlagsInteractor.kt
@@ -40,6 +40,7 @@
 import com.android.systemui.power.shared.model.WakeSleepReason
 import com.android.systemui.power.shared.model.WakefulnessModel
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
@@ -62,7 +63,7 @@
 @Inject
 constructor(
     @Application private val scope: CoroutineScope,
-    @Application private val applicationContext: Context,
+    @ShadeDisplayAware private val context: Context,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor,
     private val statusBarService: IStatusBarService,
@@ -142,7 +143,7 @@
 
         scope.launch {
             disableFlagsForUserId.collect { (selectedUserId, flags) ->
-                if (applicationContext.getSystemService(Context.STATUS_BAR_SERVICE) == null) {
+                if (context.getSystemService(Context.STATUS_BAR_SERVICE) == null) {
                     return@collect
                 }
 
@@ -151,7 +152,7 @@
                         statusBarService.disableForUser(
                             flags,
                             disableToken,
-                            applicationContext.packageName,
+                            context.packageName,
                             selectedUserId,
                         )
                     } catch (e: RemoteException) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/SwipeUpAnywhereGestureHandler.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/SwipeUpAnywhereGestureHandler.kt
index 3540a0c..b6aa209 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/SwipeUpAnywhereGestureHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/SwipeUpAnywhereGestureHandler.kt
@@ -20,6 +20,7 @@
 import android.view.MotionEvent
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.settings.DisplayTracker
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.gesture.SwipeUpGestureHandler
 import com.android.systemui.statusbar.gesture.SwipeUpGestureLogger
 import javax.inject.Inject
@@ -29,7 +30,7 @@
 class SwipeUpAnywhereGestureHandler
 @Inject
 constructor(
-    context: Context,
+    @ShadeDisplayAware context: Context,
     displayTracker: DisplayTracker,
     logger: SwipeUpGestureLogger,
 ) :
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AccessibilityActionsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AccessibilityActionsSection.kt
index 717a898..587a8fe 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AccessibilityActionsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AccessibilityActionsSection.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.keyguard.ui.binder.AccessibilityActionsViewBinder
 import com.android.systemui.keyguard.ui.viewmodel.AccessibilityActionsViewModel
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.util.Utils
 import javax.inject.Inject
 import kotlinx.coroutines.DisposableHandle
@@ -37,7 +38,7 @@
 class AccessibilityActionsSection
 @Inject
 constructor(
-    private val context: Context,
+    @ShadeDisplayAware private val context: Context,
     private val communalSettingsInteractor: CommunalSettingsInteractor,
     private val accessibilityActionsViewModel: AccessibilityActionsViewModel,
 ) : KeyguardSection() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
index d639978..8622ffc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
@@ -28,13 +28,14 @@
 import com.android.systemui.keyguard.ui.view.KeyguardRootView
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 
 /** Adds a layer to group elements for translation for burn-in preventation */
 class AodBurnInSection
 @Inject
 constructor(
-    private val context: Context,
+    @ShadeDisplayAware private val context: Context,
     private val rootView: KeyguardRootView,
     private val clockViewModel: KeyguardClockViewModel,
 ) : KeyguardSection() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
index faa4978..08c3f15 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
@@ -47,7 +47,7 @@
 class AodNotificationIconsSection
 @Inject
 constructor(
-    private val context: Context,
+    @ShadeDisplayAware private val context: Context,
     @ShadeDisplayAware private val configurationState: ConfigurationState,
     private val iconBindingFailureTracker: StatusBarIconViewBindingFailureTracker,
     private val nicAodViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index 6096cf7..c009720 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -45,6 +45,7 @@
 import com.android.systemui.plugins.clocks.ClockFaceLayout
 import com.android.systemui.res.R
 import com.android.systemui.shade.LargeScreenHeaderHelper
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.shared.R as sharedR
 import com.android.systemui.util.ui.value
 import dagger.Lazy
@@ -69,7 +70,7 @@
 constructor(
     private val clockInteractor: KeyguardClockInteractor,
     protected val keyguardClockViewModel: KeyguardClockViewModel,
-    private val context: Context,
+    @ShadeDisplayAware private val context: Context,
     val smartspaceViewModel: KeyguardSmartspaceViewModel,
     val blueprintInteractor: Lazy<KeyguardBlueprintInteractor>,
     private val rootViewModel: KeyguardRootViewModel,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
index 8d2bfb5..b51bb7b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
@@ -45,6 +45,7 @@
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.res.R
 import com.android.systemui.shade.NotificationPanelView
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.VibratorHelper
 import dagger.Lazy
 import javax.inject.Inject
@@ -60,7 +61,7 @@
     @Application private val applicationScope: CoroutineScope,
     private val authController: AuthController,
     private val windowManager: WindowManager,
-    private val context: Context,
+    @ShadeDisplayAware private val context: Context,
     private val notificationPanelView: NotificationPanelView,
     private val featureFlags: FeatureFlags,
     private val deviceEntryIconViewModel: Lazy<DeviceEntryIconViewModel>,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt
index af0528a..2d9dac4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.keyguard.ui.view.KeyguardIndicationArea
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.KeyguardIndicationController
 import javax.inject.Inject
 import kotlinx.coroutines.DisposableHandle
@@ -34,7 +35,7 @@
 class DefaultIndicationAreaSection
 @Inject
 constructor(
-    private val context: Context,
+    @ShadeDisplayAware private val context: Context,
     private val keyguardIndicationAreaViewModel: KeyguardIndicationAreaViewModel,
     private val indicationController: KeyguardIndicationController,
 ) : KeyguardSection() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
index 6ac33af..3a791fd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.res.R
 import com.android.systemui.shade.LargeScreenHeaderHelper
 import com.android.systemui.shade.NotificationPanelView
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
 import com.android.systemui.statusbar.notification.stack.ui.viewbinder.SharedNotificationContainerBinder
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
@@ -38,7 +39,7 @@
 class DefaultNotificationStackScrollLayoutSection
 @Inject
 constructor(
-    context: Context,
+    @ShadeDisplayAware context: Context,
     notificationPanelView: NotificationPanelView,
     sharedNotificationContainer: SharedNotificationContainer,
     sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusBarSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusBarSection.kt
index 9b5fae3..9d9be44 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusBarSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusBarSection.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.shade.NotificationPanelView
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.shade.ShadeViewStateProvider
 import com.android.systemui.statusbar.phone.KeyguardStatusBarView
 import com.android.systemui.util.Utils
@@ -40,7 +41,7 @@
 class DefaultStatusBarSection
 @Inject
 constructor(
-    private val context: Context,
+    @ShadeDisplayAware private val context: Context,
     private val notificationPanelView: NotificationPanelView,
     private val keyguardStatusBarViewComponentFactory: KeyguardStatusBarViewComponent.Factory,
 ) : KeyguardSection() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt
index 45641db..57ea1ad 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt
@@ -38,6 +38,7 @@
 import com.android.systemui.res.R
 import com.android.systemui.shade.NotificationPanelView
 import com.android.systemui.shade.NotificationPanelViewController
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.policy.SplitShadeStateController
 import com.android.systemui.util.Utils
 import dagger.Lazy
@@ -47,7 +48,7 @@
 class DefaultStatusViewSection
 @Inject
 constructor(
-    private val context: Context,
+    @ShadeDisplayAware private val context: Context,
     private val notificationPanelView: NotificationPanelView,
     private val keyguardStatusViewComponentFactory: KeyguardStatusViewComponent.Factory,
     private val keyguardViewConfigurator: Lazy<KeyguardViewConfigurator>,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultUdfpsAccessibilityOverlaySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultUdfpsAccessibilityOverlaySection.kt
index 2abb7ba..0ae1400 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultUdfpsAccessibilityOverlaySection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultUdfpsAccessibilityOverlaySection.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
@@ -34,7 +35,7 @@
 class DefaultUdfpsAccessibilityOverlaySection
 @Inject
 constructor(
-    private val context: Context,
+    @ShadeDisplayAware private val context: Context,
     private val viewModel: DeviceEntryUdfpsAccessibilityOverlayViewModel,
 ) : KeyguardSection() {
     private val viewId = R.id.udfps_accessibility_overlay
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
index 6ddcae3..7ad2ec5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.keyguard.ui.binder.KeyguardSmartspaceViewBinder
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.res.R as R
 import com.android.systemui.shared.R as sharedR
 import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
@@ -44,7 +45,7 @@
 open class SmartspaceSection
 @Inject
 constructor(
-    val context: Context,
+    @ShadeDisplayAware val context: Context,
     val keyguardClockViewModel: KeyguardClockViewModel,
     val keyguardSmartspaceViewModel: KeyguardSmartspaceViewModel,
     private val keyguardSmartspaceInteractor: KeyguardSmartspaceInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
index 5dbba75..0782846 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
@@ -33,13 +33,14 @@
 import com.android.systemui.media.controls.ui.controller.KeyguardMediaController
 import com.android.systemui.res.R
 import com.android.systemui.shade.NotificationPanelView
+import com.android.systemui.shade.ShadeDisplayAware
 import javax.inject.Inject
 
 /** Aligns media on left side for split shade, below smartspace, date, and weather. */
 class SplitShadeMediaSection
 @Inject
 constructor(
-    private val context: Context,
+    @ShadeDisplayAware private val context: Context,
     private val notificationPanelView: NotificationPanelView,
     private val keyguardMediaController: KeyguardMediaController
 ) : KeyguardSection() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
index 1a73866..729759a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.keyguard.MigrateClocksToBlueprint
 import com.android.systemui.res.R
 import com.android.systemui.shade.NotificationPanelView
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
 import com.android.systemui.statusbar.notification.stack.ui.viewbinder.SharedNotificationContainerBinder
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
@@ -35,7 +36,7 @@
 class SplitShadeNotificationStackScrollLayoutSection
 @Inject
 constructor(
-    context: Context,
+    @ShadeDisplayAware context: Context,
     notificationPanelView: NotificationPanelView,
     sharedNotificationContainer: SharedNotificationContainer,
     sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
index 56e3125..3a5263f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
@@ -41,7 +41,7 @@
 class AlternateBouncerUdfpsIconViewModel
 @Inject
 constructor(
-    val context: Context,
+    @ShadeDisplayAware val context: Context,
     @ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
     deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
     deviceEntryBackgroundViewModel: DeviceEntryBackgroundViewModel,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
index 12f9467..29ae4b9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
@@ -40,7 +40,7 @@
 class DeviceEntryBackgroundViewModel
 @Inject
 constructor(
-    val context: Context,
+    @ShadeDisplayAware val context: Context,
     val deviceEntryIconViewModel: DeviceEntryIconViewModel,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
     @ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
index 749f193..5065fcb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
@@ -45,7 +45,7 @@
 class DeviceEntryForegroundViewModel
 @Inject
 constructor(
-    val context: Context,
+    @ShadeDisplayAware val context: Context,
     @ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
     deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
     transitionInteractor: KeyguardTransitionInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
index da96f3f..3de1f1e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
@@ -40,6 +40,7 @@
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.phone.DozeServiceHost
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
@@ -66,7 +67,7 @@
 class SideFpsProgressBarViewModel
 @Inject
 constructor(
-    private val context: Context,
+    @ShadeDisplayAware private val context: Context,
     biometricStatusInteractor: BiometricStatusInteractor,
     deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
     private val sfpsSensorInteractor: SideFpsSensorInteractor,
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 ec6a17b..21c45c5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -51,6 +51,7 @@
 import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.LaunchedEffect
@@ -78,6 +79,7 @@
 import androidx.compose.ui.semantics.customActions
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.round
 import androidx.compose.ui.util.fastRoundToInt
 import androidx.compose.ui.viewinterop.AndroidView
@@ -102,6 +104,8 @@
 import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer
 import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyboard.shortcut.ui.composable.InteractionsConfig
+import com.android.systemui.keyboard.shortcut.ui.composable.ProvideShortcutHelperIndication
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.lifecycle.setSnapshotBinding
 import com.android.systemui.media.controls.ui.view.MediaHost
@@ -240,51 +244,54 @@
 
     @Composable
     private fun Content() {
-        PlatformTheme {
-            AnimatedVisibility(
-                visible = viewModel.isQsVisible,
-                modifier =
-                    Modifier.graphicsLayer { alpha = viewModel.viewAlpha }
-                        // Clipping before translation to match QSContainerImpl.onDraw
-                        .offset {
-                            IntOffset(x = 0, y = viewModel.viewTranslationY.fastRoundToInt())
-                        }
-                        .thenIf(notificationScrimClippingParams.isEnabled) {
-                            Modifier.notificationScrimClip {
-                                notificationScrimClippingParams.params
+        PlatformTheme(isDarkTheme = true) {
+            ProvideShortcutHelperIndication(interactionsConfig = interactionsConfig()) {
+                AnimatedVisibility(
+                    visible = viewModel.isQsVisible,
+                    modifier =
+                        Modifier.graphicsLayer { alpha = viewModel.viewAlpha }
+                            // Clipping before translation to match QSContainerImpl.onDraw
+                            .offset {
+                                IntOffset(x = 0, y = viewModel.viewTranslationY.fastRoundToInt())
                             }
+                            .thenIf(notificationScrimClippingParams.isEnabled) {
+                                Modifier.notificationScrimClip {
+                                    notificationScrimClippingParams.params
+                                }
+                            }
+                            // Disable touches in the whole composable while the mirror is showing.
+                            // While the mirror is showing, an ancestor of the ComposeView is made
+                            // alpha 0, but touches are still being captured by the composables.
+                            .gesturesDisabled(viewModel.showingMirror),
+                ) {
+                    val isEditing by
+                        viewModel.containerViewModel.editModeViewModel.isEditing
+                            .collectAsStateWithLifecycle()
+                    val animationSpecEditMode = tween<Float>(EDIT_MODE_TIME_MILLIS)
+                    AnimatedContent(
+                        targetState = isEditing,
+                        transitionSpec = {
+                            fadeIn(animationSpecEditMode) togetherWith
+                                fadeOut(animationSpecEditMode)
+                        },
+                        label = "EditModeAnimatedContent",
+                    ) { editing ->
+                        if (editing) {
+                            val qqsPadding = viewModel.qqsHeaderHeight
+                            EditMode(
+                                viewModel = viewModel.containerViewModel.editModeViewModel,
+                                modifier =
+                                    Modifier.fillMaxWidth()
+                                        .padding(top = { qqsPadding })
+                                        .padding(
+                                            horizontal = {
+                                                QuickSettingsShade.Dimensions.Padding.roundToPx()
+                                            }
+                                        ),
+                            )
+                        } else {
+                            CollapsableQuickSettingsSTL()
                         }
-                        // Disable touches in the whole composable while the mirror is showing.
-                        // While the mirror is showing, an ancestor of the ComposeView is made
-                        // alpha 0, but touches are still being captured by the composables.
-                        .gesturesDisabled(viewModel.showingMirror),
-            ) {
-                val isEditing by
-                    viewModel.containerViewModel.editModeViewModel.isEditing
-                        .collectAsStateWithLifecycle()
-                val animationSpecEditMode = tween<Float>(EDIT_MODE_TIME_MILLIS)
-                AnimatedContent(
-                    targetState = isEditing,
-                    transitionSpec = {
-                        fadeIn(animationSpecEditMode) togetherWith fadeOut(animationSpecEditMode)
-                    },
-                    label = "EditModeAnimatedContent",
-                ) { editing ->
-                    if (editing) {
-                        val qqsPadding = viewModel.qqsHeaderHeight
-                        EditMode(
-                            viewModel = viewModel.containerViewModel.editModeViewModel,
-                            modifier =
-                                Modifier.fillMaxWidth()
-                                    .padding(top = { qqsPadding })
-                                    .padding(
-                                        horizontal = {
-                                            QuickSettingsShade.Dimensions.Padding.roundToPx()
-                                        }
-                                    ),
-                        )
-                    } else {
-                        CollapsableQuickSettingsSTL()
                     }
                 }
             }
@@ -1090,3 +1097,14 @@
     const val qsScroll = "expanded_qs_scroll_view"
     const val qsFooterActions = "qs_footer_actions"
 }
+
+@Composable
+private fun interactionsConfig() =
+    InteractionsConfig(
+        hoverOverlayColor = MaterialTheme.colorScheme.onSurface,
+        hoverOverlayAlpha = 0.11f,
+        pressedOverlayColor = MaterialTheme.colorScheme.onSurface,
+        pressedOverlayAlpha = 0.15f,
+        // we are OK using this as our content is clipped and all corner radius are larger than this
+        surfaceCornerRadius = 28.dp,
+    )
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 2efe500..4e094cc 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
@@ -18,10 +18,13 @@
 
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.requiredHeight
 import androidx.compose.foundation.pager.HorizontalPager
 import androidx.compose.foundation.pager.rememberPagerState
+import androidx.compose.foundation.shape.CornerSize
+import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Edit
 import androidx.compose.material3.Icon
@@ -32,7 +35,6 @@
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.snapshotFlow
 import androidx.compose.ui.Alignment
@@ -41,6 +43,7 @@
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.scene.SceneScope
+import com.android.compose.modifiers.padding
 import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.lifecycle.rememberViewModel
 import com.android.systemui.qs.panels.dagger.PaginatedBaseLayoutType
@@ -48,6 +51,7 @@
 import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout.Dimensions.InterPageSpacing
 import com.android.systemui.qs.panels.ui.viewmodel.PaginatedGridViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
+import com.android.systemui.qs.ui.compose.borderOnFocus
 import com.android.systemui.res.R
 import javax.inject.Inject
 
@@ -89,9 +93,24 @@
         }
 
         Column {
+            val contentPaddingValue =
+                if (pages.size > 1) {
+                    InterPageSpacing
+                } else {
+                    0.dp
+                }
+            val contentPadding = PaddingValues(horizontal = contentPaddingValue)
+
+            /* Use negative padding equal with value equal to content padding. That way, each page
+             * layout extends to the sides, but the content is as if there was no padding. That
+             * way, the clipping bounds of the HorizontalPager extend beyond the tiles in each page.
+             */
             HorizontalPager(
                 state = pagerState,
-                modifier = Modifier.sysuiResTag("qs_pager"),
+                modifier =
+                    Modifier.sysuiResTag("qs_pager")
+                        .padding(horizontal = { -contentPaddingValue.roundToPx() }),
+                contentPadding = contentPadding,
                 pageSpacing = if (pages.size > 1) InterPageSpacing else 0.dp,
                 beyondViewportPageCount = 1,
                 verticalAlignment = Alignment.Top,
@@ -114,7 +133,13 @@
                 CompositionLocalProvider(value = LocalContentColor provides Color.White) {
                     IconButton(
                         onClick = editModeStart,
-                        modifier = Modifier.align(Alignment.CenterEnd),
+                        shape = RoundedCornerShape(CornerSize(28.dp)),
+                        modifier =
+                            Modifier.align(Alignment.CenterEnd)
+                                .borderOnFocus(
+                                    color = MaterialTheme.colorScheme.secondary,
+                                    cornerSize = CornerSize(FooterHeight / 2),
+                                ),
                     ) {
                         Icon(
                             imageVector = Icons.Default.Edit,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
index 177a5be..0e09ad2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
@@ -50,7 +50,6 @@
 import androidx.compose.ui.draw.drawBehind
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.ColorFilter
-import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.semantics.Role
@@ -75,6 +74,7 @@
 import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.SideIconWidth
 import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.longPressLabel
 import com.android.systemui.qs.panels.ui.viewmodel.AccessibilityUiState
+import com.android.systemui.qs.ui.compose.borderOnFocus
 import com.android.systemui.res.R
 
 private const val TEST_TAG_TOGGLE = "qs_tile_toggle_target"
@@ -88,7 +88,7 @@
     colors: TileColors,
     squishiness: () -> Float,
     accessibilityUiState: AccessibilityUiState? = null,
-    iconShape: Shape = RoundedCornerShape(CommonTileDefaults.InactiveCornerRadius),
+    iconShape: RoundedCornerShape = RoundedCornerShape(CommonTileDefaults.InactiveCornerRadius),
     toggleClick: (() -> Unit)? = null,
     onLongClick: (() -> Unit)? = null,
 ) {
@@ -100,10 +100,12 @@
         val longPressLabel = longPressLabel().takeIf { onLongClick != null }
         val animatedBackgroundColor by
             animateColorAsState(colors.iconBackground, label = "QSTileDualTargetBackgroundColor")
+        val focusBorderColor = MaterialTheme.colorScheme.secondary
         Box(
             modifier =
                 Modifier.size(CommonTileDefaults.ToggleTargetSize).thenIf(toggleClick != null) {
-                    Modifier.clip(iconShape)
+                    Modifier.borderOnFocus(color = focusBorderColor, iconShape.topEnd)
+                        .clip(iconShape)
                         .verticalSquish(squishiness)
                         .drawBehind { drawRect(animatedBackgroundColor) }
                         .combinedClickable(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
index fe59c4d3..cb57c67 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
@@ -39,6 +39,7 @@
 import androidx.compose.foundation.lazy.grid.LazyGridState
 import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
 import androidx.compose.foundation.lazy.grid.rememberLazyGridState
+import androidx.compose.foundation.shape.CornerSize
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
@@ -49,6 +50,7 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
+import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.platform.LocalConfiguration
@@ -59,6 +61,7 @@
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.semantics.stateDescription
 import androidx.compose.ui.semantics.toggleableState
+import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -82,6 +85,7 @@
 import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.toUiState
 import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.ui.compose.borderOnFocus
 import com.android.systemui.res.R
 import java.util.function.Supplier
 import kotlinx.coroutines.CoroutineScope
@@ -139,6 +143,7 @@
         hapticsViewModel = hapticsViewModel,
         modifier =
             modifier
+                .borderOnFocus(color = MaterialTheme.colorScheme.secondary, tileShape.topEnd)
                 .fillMaxWidth()
                 .bounceable(
                     bounceable = currentBounceableInfo.bounceable,
@@ -381,7 +386,7 @@
     }
 
     @Composable
-    fun animateIconShape(state: Int): Shape {
+    fun animateIconShape(state: Int): RoundedCornerShape {
         return animateShape(
             state = state,
             activeCornerRadius = ActiveIconCornerRadius,
@@ -390,7 +395,7 @@
     }
 
     @Composable
-    fun animateTileShape(state: Int): Shape {
+    fun animateTileShape(state: Int): RoundedCornerShape {
         return animateShape(
             state = state,
             activeCornerRadius = ActiveTileCornerRadius,
@@ -399,7 +404,7 @@
     }
 
     @Composable
-    fun animateShape(state: Int, activeCornerRadius: Dp, label: String): Shape {
+    fun animateShape(state: Int, activeCornerRadius: Dp, label: String): RoundedCornerShape {
         val animatedCornerRadius by
             animateDpAsState(
                 targetValue =
@@ -410,7 +415,15 @@
                     },
                 label = label,
             )
-        return RoundedCornerShape(animatedCornerRadius)
+
+        val corner = remember {
+            object : CornerSize {
+                override fun toPx(shapeSize: Size, density: Density): Float {
+                    return with(density) { animatedCornerRadius.toPx() }
+                }
+            }
+        }
+        return RoundedCornerShape(corner)
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 301ab2b..8f6c4e7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -1429,7 +1429,7 @@
     void makeOverlayToast(int stringId) {
         final Resources res = mContext.getResources();
 
-        final SystemUIToast systemUIToast = mToastFactory.createToast(mContext,
+        final SystemUIToast systemUIToast = mToastFactory.createToast(mContext, mContext,
                 res.getString(stringId), mContext.getPackageName(), UserHandle.myUserId(),
                 res.getConfiguration().orientation);
         if (systemUIToast == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/compose/BorderOnFocus.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/compose/BorderOnFocus.kt
new file mode 100644
index 0000000..e6caa0d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/compose/BorderOnFocus.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.ui.compose
+
+import androidx.compose.foundation.shape.CornerSize
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusEventModifierNode
+import androidx.compose.ui.focus.FocusState
+import androidx.compose.ui.geometry.CornerRadius
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.drawscope.ContentDrawScope
+import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.node.DrawModifierNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.platform.InspectorInfo
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+
+/**
+ * Provides a rounded rect border when the element is focused.
+ *
+ * This should be used for elements that are themselves rounded rects.
+ */
+fun Modifier.borderOnFocus(
+    color: Color,
+    cornerSize: CornerSize,
+    strokeWidth: Dp = 3.dp,
+    padding: Dp = 2.dp,
+) = this then BorderOnFocusElement(color, cornerSize, strokeWidth, padding)
+
+private class BorderOnFocusNode(
+    var color: Color,
+    var cornerSize: CornerSize,
+    var strokeWidth: Dp,
+    var padding: Dp,
+) : FocusEventModifierNode, DrawModifierNode, Modifier.Node() {
+
+    private var focused by mutableStateOf(false)
+
+    override fun onFocusEvent(focusState: FocusState) {
+        focused = focusState.isFocused
+    }
+
+    override fun ContentDrawScope.draw() {
+        drawContent()
+        val focusOutline = Rect(Offset.Zero, size).inflate(padding.toPx())
+        if (focused) {
+            drawRoundRect(
+                color = color,
+                topLeft = focusOutline.topLeft,
+                size = focusOutline.size,
+                cornerRadius = CornerRadius(cornerSize.toPx(focusOutline.size, this)),
+                style = Stroke(strokeWidth.toPx()),
+            )
+        }
+    }
+}
+
+private data class BorderOnFocusElement(
+    val color: Color,
+    val cornerSize: CornerSize,
+    val strokeWidth: Dp,
+    val padding: Dp,
+) : ModifierNodeElement<BorderOnFocusNode>() {
+    override fun create(): BorderOnFocusNode {
+        return BorderOnFocusNode(color, cornerSize, strokeWidth, padding)
+    }
+
+    override fun update(node: BorderOnFocusNode) {
+        node.color = color
+        node.cornerSize = cornerSize
+        node.strokeWidth = strokeWidth
+        node.padding = padding
+    }
+
+    override fun InspectorInfo.inspectableProperties() {
+        name = "borderOnFocus"
+        properties["color"] = color
+        properties["cornerSize"] = cornerSize
+        properties["strokeWidth"] = strokeWidth
+        properties["padding"] = padding
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/BrightnessWarningToast.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/BrightnessWarningToast.kt
index dfbdaa6..9dc2cba 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/BrightnessWarningToast.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/BrightnessWarningToast.kt
@@ -41,7 +41,7 @@
         // Show the brightness warning toast with passing the toast inflation required context,
         // userId and resId from SystemUI package.
         val systemUIToast = toastFactory.createToast(
-            viewContext,
+            viewContext, viewContext,
             res.getString(resId), viewContext.packageName, viewContext.getUserId(),
             res.configuration.orientation
         )
diff --git a/packages/SystemUI/src/com/android/systemui/shade/StatusBarLongPressGestureDetector.kt b/packages/SystemUI/src/com/android/systemui/shade/LongPressGestureDetector.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/shade/StatusBarLongPressGestureDetector.kt
rename to packages/SystemUI/src/com/android/systemui/shade/LongPressGestureDetector.kt
index ae36e81..6fb3ca5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/StatusBarLongPressGestureDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/LongPressGestureDetector.kt
@@ -25,7 +25,7 @@
 
 /** Accepts touch events, detects long press, and calls ShadeViewController#onStatusBarLongPress. */
 @SysUISingleton
-class StatusBarLongPressGestureDetector
+class LongPressGestureDetector
 @Inject
 constructor(context: Context, val shadeViewController: ShadeViewController) {
     val gestureDetector =
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index c15c8f9..0e82bf8 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -2053,9 +2053,6 @@
         }
         if (mQsController.getExpanded()) {
             mQsController.flingQs(0, FLING_COLLAPSE);
-        } else if (mBarState == KEYGUARD) {
-            mLockscreenShadeTransitionController.goToLockedShade(
-                    /* expandedView= */null, /* needsQSAnimation= */false);
         } else {
             expand(true /* animate */);
         }
@@ -3112,7 +3109,7 @@
         if (isTracking()) {
             onTrackingStopped(true);
         }
-        if (isExpanded() && mBarState != KEYGUARD && !mQsController.getExpanded()) {
+        if (isExpanded() && !mQsController.getExpanded()) {
             mShadeLog.d("Status Bar was long pressed. Expanding to QS.");
             expandToQs();
         } else {
@@ -5094,13 +5091,6 @@
             }
             boolean handled = mHeadsUpTouchHelper.onTouchEvent(event);
 
-            // This touch session has already resulted in shade expansion. Ignore everything else.
-            if (ShadeExpandsOnStatusBarLongPress.isEnabled()
-                    && event.getActionMasked() != MotionEvent.ACTION_DOWN
-                    && event.getDownTime() == mStatusBarLongPressDowntime) {
-                mShadeLog.d("Touch has same down time as Status Bar long press. Ignoring.");
-                return false;
-            }
             if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && mQsController.handleTouch(
                     event, isFullyCollapsed(), isShadeOrQsHeightAnimationRunning())) {
                 if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
@@ -5108,6 +5098,13 @@
                 }
                 return true;
             }
+            // This touch session has already resulted in shade expansion. Ignore everything else.
+            if (ShadeExpandsOnStatusBarLongPress.isEnabled()
+                    && event.getActionMasked() != MotionEvent.ACTION_DOWN
+                    && event.getDownTime() == mStatusBarLongPressDowntime) {
+                mShadeLog.d("Touch has same down time as Status Bar long press. Ignoring.");
+                return false;
+            }
             if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
                 mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
                 handled = true;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
new file mode 100644
index 0000000..4e7898d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
@@ -0,0 +1,110 @@
+/*
+ * 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.shade.domain.interactor
+
+import android.content.Context
+import android.util.Log
+import android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE
+import com.android.app.tracing.coroutines.launchTraced
+import com.android.app.tracing.traceSection
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
+import com.android.systemui.display.shared.model.DisplayWindowProperties
+import com.android.systemui.scene.ui.view.WindowRootView
+import com.android.systemui.shade.ShadeDisplayAware
+import com.android.systemui.shade.ShadeWindowLayoutParams
+import com.android.systemui.shade.data.repository.ShadePositionRepository
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
+import com.android.systemui.statusbar.phone.ConfigurationForwarder
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.withContext
+
+/** Handles Shade window display change when [ShadePositionRepository.displayId] changes. */
+@SysUISingleton
+class ShadeDisplaysInteractor
+@Inject
+constructor(
+    private val shadeRootView: WindowRootView,
+    private val shadePositionRepository: ShadePositionRepository,
+    @ShadeDisplayAware private val shadeContext: Context,
+    private val displayWindowPropertiesRepository: DisplayWindowPropertiesRepository,
+    @Background private val bgScope: CoroutineScope,
+    @ShadeDisplayAware private val configurationForwarder: ConfigurationForwarder,
+    @Main private val mainContext: CoroutineContext,
+) : CoreStartable {
+
+    override fun start() {
+        ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()
+        bgScope.launchTraced(TAG) {
+            shadePositionRepository.displayId.collect { displayId -> moveShadeWindowTo(displayId) }
+        }
+    }
+
+    /** Tries to move the shade. If anything wrong happens, fails gracefully without crashing. */
+    private suspend fun moveShadeWindowTo(destinationDisplayId: Int) {
+        val currentId = shadeRootView.display.displayId
+        if (currentId == destinationDisplayId) {
+            Log.w(TAG, "Trying to move the shade to a display it was already in")
+            return
+        }
+        try {
+            moveShadeWindow(fromId = currentId, toId = destinationDisplayId)
+        } catch (e: IllegalStateException) {
+            Log.e(
+                TAG,
+                "Unable to move the shade window from display $currentId to $destinationDisplayId",
+                e,
+            )
+        }
+    }
+
+    private suspend fun moveShadeWindow(fromId: Int, toId: Int) {
+        val sourceProperties = getDisplayWindowProperties(fromId)
+        val destinationProperties = getDisplayWindowProperties(toId)
+        traceSection({ "MovingShadeWindow from $fromId to $toId" }) {
+            withContext(mainContext) {
+                traceSection("removeView") {
+                    sourceProperties.windowManager.removeView(shadeRootView)
+                }
+                traceSection("addView") {
+                    destinationProperties.windowManager.addView(
+                        shadeRootView,
+                        ShadeWindowLayoutParams.create(shadeContext),
+                    )
+                }
+            }
+        }
+        traceSection("SecondaryShadeInteractor#onConfigurationChanged") {
+            configurationForwarder.onConfigurationChanged(
+                destinationProperties.context.resources.configuration
+            )
+        }
+    }
+
+    private fun getDisplayWindowProperties(displayId: Int): DisplayWindowProperties {
+        return displayWindowPropertiesRepository.get(displayId, TYPE_NOTIFICATION_SHADE)
+    }
+
+    private companion object {
+        const val TAG = "SecondaryShadeInteractor"
+    }
+}
diff --git a/core/java/android/text/ClientFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipLogTags.kt
similarity index 60%
copy from core/java/android/text/ClientFlags.java
copy to packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipLogTags.kt
index ca88764..6c1d6c5 100644
--- a/core/java/android/text/ClientFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipLogTags.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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.
@@ -14,16 +14,13 @@
  * limitations under the License.
  */
 
-package android.text;
+package com.android.systemui.statusbar.chips
 
-/**
- * An aconfig feature flags that can be accessible from application process without
- * ContentProvider IPCs.
- *
- * When you add new flags, you have to add flag string to {@link TextFlags#TEXT_ACONFIGS_FLAGS}.
- *
- * TODO(nona): Remove this class.
- * @hide
- */
-public class ClientFlags {
+/** Helper class to ensure all tags used in [StatusBarChipsLog] are exactly the same length. */
+object StatusBarChipLogTags {
+    private const val TAG_LENGTH = 20
+
+    fun String.pad(): String {
+        return this.padEnd(TAG_LENGTH)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt
index eaefc11..bb0467f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/domain/interactor/CallChipInteractor.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel
+import com.android.systemui.statusbar.chips.StatusBarChipLogTags.pad
 import com.android.systemui.statusbar.chips.StatusBarChipsLog
 import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
 import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
@@ -47,6 +48,6 @@
             .stateIn(scope, SharingStarted.Lazily, OngoingCallModel.NoCall)
 
     companion object {
-        private const val TAG = "OngoingCall"
+        private val TAG = "OngoingCall".pad()
     }
 }
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 e825258..b8cdd25 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
@@ -28,6 +28,7 @@
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.res.R
+import com.android.systemui.statusbar.chips.StatusBarChipLogTags.pad
 import com.android.systemui.statusbar.chips.StatusBarChipsLog
 import com.android.systemui.statusbar.chips.call.domain.interactor.CallChipInteractor
 import com.android.systemui.statusbar.chips.ui.model.ColorsModel
@@ -112,7 +113,7 @@
                 ActivityTransitionAnimator.Controller.fromView(
                     backgroundView,
                     InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
-                )
+                ),
             )
         }
     }
@@ -121,10 +122,8 @@
         private val phoneIcon =
             Icon.Resource(
                 com.android.internal.R.drawable.ic_phone,
-                ContentDescription.Resource(
-                    R.string.ongoing_phone_call_content_description,
-                ),
+                ContentDescription.Resource(R.string.ongoing_phone_call_content_description),
             )
-        private const val TAG = "CallVM"
+        private val TAG = "CallVM".pad()
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt
index 7c95f1e..b3dbf29 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/domain/interactor/MediaRouterChipInteractor.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.mediarouter.data.repository.MediaRouterRepository
+import com.android.systemui.statusbar.chips.StatusBarChipLogTags.pad
 import com.android.systemui.statusbar.chips.StatusBarChipsLog
 import com.android.systemui.statusbar.chips.casttootherdevice.domain.model.MediaRouterCastModel
 import com.android.systemui.statusbar.policy.CastDevice
@@ -68,6 +69,6 @@
     }
 
     companion object {
-        private const val TAG = "MediaRouter"
+        private val TAG = "MediaRouter".pad()
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
index 1107206..3422337 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/casttootherdevice/ui/viewmodel/CastToOtherDeviceChipViewModel.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.res.R
+import com.android.systemui.statusbar.chips.StatusBarChipLogTags.pad
 import com.android.systemui.statusbar.chips.StatusBarChipsLog
 import com.android.systemui.statusbar.chips.casttootherdevice.domain.interactor.MediaRouterChipInteractor
 import com.android.systemui.statusbar.chips.casttootherdevice.domain.model.MediaRouterCastModel
@@ -255,6 +256,6 @@
 
     companion object {
         @DrawableRes val CAST_TO_OTHER_DEVICE_ICON = R.drawable.ic_cast_connected
-        private const val TAG = "CastToOtherVM"
+        private val TAG = "CastToOtherVM".pad()
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
index 27b2465..af238f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/domain/interactor/MediaProjectionChipInteractor.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.chips.mediaprojection.domain.interactor
 
 import android.content.pm.PackageManager
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.Flags
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -25,6 +26,7 @@
 import com.android.systemui.mediaprojection.MediaProjectionUtils.packageHasCastingCapabilities
 import com.android.systemui.mediaprojection.data.model.MediaProjectionState
 import com.android.systemui.mediaprojection.data.repository.MediaProjectionRepository
+import com.android.systemui.statusbar.chips.StatusBarChipLogTags.pad
 import com.android.systemui.statusbar.chips.StatusBarChipsLog
 import com.android.systemui.statusbar.chips.mediaprojection.domain.model.ProjectionChipModel
 import javax.inject.Inject
@@ -33,7 +35,6 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /**
  * Interactor for media projection events, used to show chips in the status bar for share-to-app and
@@ -108,6 +109,6 @@
     }
 
     companion object {
-        private const val TAG = "MediaProjection"
+        private val TAG = "MediaProjection".pad()
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt
index e3dc70a..f5952f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.mediaprojection.data.repository.MediaProjectionRepository
 import com.android.systemui.screenrecord.data.model.ScreenRecordModel
 import com.android.systemui.screenrecord.data.repository.ScreenRecordRepository
+import com.android.systemui.statusbar.chips.StatusBarChipLogTags.pad
 import com.android.systemui.statusbar.chips.StatusBarChipsLog
 import com.android.systemui.statusbar.chips.screenrecord.domain.model.ScreenRecordChipModel
 import javax.inject.Inject
@@ -143,6 +144,6 @@
     }
 
     companion object {
-        private const val TAG = "ScreenRecord"
+        private val TAG = "ScreenRecord".pad()
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
index eb73521..0065593 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModel.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.res.R
 import com.android.systemui.screenrecord.data.model.ScreenRecordModel.Starting.Companion.toCountdownSeconds
+import com.android.systemui.statusbar.chips.StatusBarChipLogTags.pad
 import com.android.systemui.statusbar.chips.StatusBarChipsLog
 import com.android.systemui.statusbar.chips.mediaprojection.ui.view.EndMediaProjectionDialogHelper
 import com.android.systemui.statusbar.chips.screenrecord.domain.interactor.ScreenRecordChipInteractor
@@ -84,7 +85,7 @@
                                     Icon.Resource(
                                         ICON,
                                         ContentDescription.Resource(
-                                            R.string.screenrecord_ongoing_screen_only,
+                                            R.string.screenrecord_ongoing_screen_only
                                         ),
                                     )
                                 ),
@@ -153,6 +154,6 @@
 
     companion object {
         @DrawableRes val ICON = R.drawable.ic_screenrecord
-        private const val TAG = "ScreenRecordVM"
+        private val TAG = "ScreenRecordVM".pad()
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
index 11d077f..2af86a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/sharetoapp/ui/viewmodel/ShareToAppChipViewModel.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.res.R
+import com.android.systemui.statusbar.chips.StatusBarChipLogTags.pad
 import com.android.systemui.statusbar.chips.StatusBarChipsLog
 import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractor
 import com.android.systemui.statusbar.chips.mediaprojection.domain.model.ProjectionChipModel
@@ -179,6 +180,6 @@
 
     companion object {
         @DrawableRes val SHARE_TO_APP_ICON = R.drawable.ic_present_to_all
-        private const val TAG = "ShareToAppVM"
+        private val TAG = "ShareToAppVM".pad()
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
index ed32597..45efc57 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel
+import com.android.systemui.statusbar.chips.StatusBarChipLogTags.pad
 import com.android.systemui.statusbar.chips.StatusBarChipsLog
 import com.android.systemui.statusbar.chips.call.ui.viewmodel.CallChipViewModel
 import com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel.CastToOtherDeviceChipViewModel
@@ -347,7 +348,7 @@
     }
 
     companion object {
-        private const val TAG = "ChipsViewModel"
+        private val TAG = "ChipsViewModel".pad()
 
         private val DEFAULT_INTERNAL_HIDDEN_MODEL =
             InternalChipModel.Hidden(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpManagerPhone.java
deleted file mode 100644
index 0299ebc..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpManagerPhone.java
+++ /dev/null
@@ -1,772 +0,0 @@
-/*
- * 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 android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Region;
-import android.os.Handler;
-import android.util.ArrayMap;
-import android.util.Pools;
-
-import androidx.collection.ArraySet;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.policy.SystemBarUtils;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.res.R;
-import com.android.systemui.scene.shared.flag.SceneContainerFlag;
-import com.android.systemui.shade.domain.interactor.ShadeInteractor;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener;
-import com.android.systemui.statusbar.notification.collection.provider.OnReorderingBannedListener;
-import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
-import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
-import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository;
-import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRepository;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
-import com.android.systemui.statusbar.phone.ExpandHeadsUpOnInlineReply;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
-import com.android.systemui.statusbar.policy.AnimationStateHandler;
-import com.android.systemui.statusbar.policy.AvalancheController;
-import com.android.systemui.statusbar.policy.BaseHeadsUpManager;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
-import com.android.systemui.statusbar.policy.OnHeadsUpPhoneListenerChange;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-import com.android.systemui.util.kotlin.JavaAdapter;
-import com.android.systemui.util.settings.GlobalSettings;
-import com.android.systemui.util.time.SystemClock;
-
-import kotlinx.coroutines.flow.Flow;
-import kotlinx.coroutines.flow.MutableStateFlow;
-import kotlinx.coroutines.flow.StateFlow;
-import kotlinx.coroutines.flow.StateFlowKt;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-import java.util.Stack;
-
-import javax.inject.Inject;
-
-/** A implementation of HeadsUpManager for phone. */
-@SysUISingleton
-public class HeadsUpManagerPhone extends BaseHeadsUpManager implements
-        HeadsUpRepository, OnHeadsUpChangedListener {
-    private static final String TAG = "HeadsUpManagerPhone";
-
-    @VisibleForTesting
-    public final int mExtensionTime;
-    private final KeyguardBypassController mBypassController;
-    private final GroupMembershipManager mGroupMembershipManager;
-    private final List<OnHeadsUpPhoneListenerChange> mHeadsUpPhoneListeners = new ArrayList<>();
-    private final VisualStabilityProvider mVisualStabilityProvider;
-
-    private AvalancheController mAvalancheController;
-
-    // TODO(b/328393698) move the topHeadsUpRow logic to an interactor
-    private final MutableStateFlow<HeadsUpRowRepository> mTopHeadsUpRow =
-            StateFlowKt.MutableStateFlow(null);
-    private final MutableStateFlow<Set<HeadsUpRowRepository>> mHeadsUpNotificationRows =
-            StateFlowKt.MutableStateFlow(new HashSet<>());
-    private final MutableStateFlow<Boolean> mHeadsUpAnimatingAway =
-            StateFlowKt.MutableStateFlow(false);
-    private boolean mReleaseOnExpandFinish;
-    private boolean mTrackingHeadsUp;
-    private final HashSet<String> mSwipedOutKeys = new HashSet<>();
-    private final HashSet<NotificationEntry> mEntriesToRemoveAfterExpand = new HashSet<>();
-    @VisibleForTesting
-    public final ArraySet<NotificationEntry> mEntriesToRemoveWhenReorderingAllowed
-            = new ArraySet<>();
-    private boolean mIsShadeOrQsExpanded;
-    private boolean mIsQsExpanded;
-    private int mStatusBarState;
-    private AnimationStateHandler mAnimationStateHandler;
-
-    private int mHeadsUpInset;
-
-    // Used for determining the region for touch interaction
-    private final Region mTouchableRegion = new Region();
-
-    private final Pools.Pool<HeadsUpEntryPhone> mEntryPool = new Pools.Pool<HeadsUpEntryPhone>() {
-        private Stack<HeadsUpEntryPhone> mPoolObjects = new Stack<>();
-
-        @Override
-        public HeadsUpEntryPhone acquire() {
-            NotificationThrottleHun.assertInLegacyMode();
-            if (!mPoolObjects.isEmpty()) {
-                return mPoolObjects.pop();
-            }
-            return new HeadsUpEntryPhone();
-        }
-
-        @Override
-        public boolean release(@NonNull HeadsUpEntryPhone instance) {
-            NotificationThrottleHun.assertInLegacyMode();
-            mPoolObjects.push(instance);
-            return true;
-        }
-    };
-
-    ///////////////////////////////////////////////////////////////////////////////////////////////
-    //  Constructor:
-    @Inject
-    public HeadsUpManagerPhone(
-            @NonNull final Context context,
-            HeadsUpManagerLogger logger,
-            StatusBarStateController statusBarStateController,
-            KeyguardBypassController bypassController,
-            GroupMembershipManager groupMembershipManager,
-            VisualStabilityProvider visualStabilityProvider,
-            ConfigurationController configurationController,
-            @Main Handler handler,
-            GlobalSettings globalSettings,
-            SystemClock systemClock,
-            @Main DelayableExecutor executor,
-            AccessibilityManagerWrapper accessibilityManagerWrapper,
-            UiEventLogger uiEventLogger,
-            JavaAdapter javaAdapter,
-            ShadeInteractor shadeInteractor,
-            AvalancheController avalancheController) {
-        super(context, logger, handler, globalSettings, systemClock, executor,
-                accessibilityManagerWrapper, uiEventLogger, avalancheController);
-        Resources resources = mContext.getResources();
-        mExtensionTime = resources.getInteger(R.integer.ambient_notification_extension_time);
-        statusBarStateController.addCallback(mStatusBarStateListener);
-        mBypassController = bypassController;
-        mGroupMembershipManager = groupMembershipManager;
-        mVisualStabilityProvider = visualStabilityProvider;
-        mAvalancheController = avalancheController;
-        updateResources();
-        configurationController.addCallback(new ConfigurationController.ConfigurationListener() {
-            @Override
-            public void onDensityOrFontScaleChanged() {
-                updateResources();
-            }
-
-            @Override
-            public void onThemeChanged() {
-                updateResources();
-            }
-        });
-        javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(),
-                    this::onShadeOrQsExpanded);
-        if (SceneContainerFlag.isEnabled()) {
-            javaAdapter.alwaysCollectFlow(shadeInteractor.isQsExpanded(),
-                    this::onQsExpanded);
-        }
-        if (NotificationThrottleHun.isEnabled()) {
-            mVisualStabilityProvider.addPersistentReorderingBannedListener(
-                    mOnReorderingBannedListener);
-            mVisualStabilityProvider.addPersistentReorderingAllowedListener(
-                    mOnReorderingAllowedListener);
-        }
-    }
-
-    public void setAnimationStateHandler(AnimationStateHandler handler) {
-        mAnimationStateHandler = handler;
-    }
-
-    private void updateResources() {
-        Resources resources = mContext.getResources();
-        mHeadsUpInset = SystemBarUtils.getStatusBarHeight(mContext)
-                + resources.getDimensionPixelSize(R.dimen.heads_up_status_bar_padding);
-    }
-
-    ///////////////////////////////////////////////////////////////////////////////////////////////
-    //  Public methods:
-
-    /**
-     * Add a listener to receive callbacks {@link #setHeadsUpAnimatingAway(boolean)}
-     */
-    @Override
-    public void addHeadsUpPhoneListener(OnHeadsUpPhoneListenerChange listener) {
-        mHeadsUpPhoneListeners.add(listener);
-    }
-
-    /**
-     * Gets the touchable region needed for heads up notifications. Returns null if no touchable
-     * region is required (ie: no heads up notification currently exists).
-     */
-    // TODO(b/347007367): With scene container enabled this method may report outdated regions
-    @Override
-    public @Nullable Region getTouchableRegion() {
-        NotificationEntry topEntry = getTopEntry();
-
-        // This call could be made in an inconsistent state while the pinnedMode hasn't been
-        // updated yet, but callbacks leading out of the headsUp manager, querying it. Let's
-        // therefore also check if the topEntry is null.
-        if (!hasPinnedHeadsUp() || topEntry == null) {
-            return null;
-        } else {
-            if (topEntry.rowIsChildInGroup()) {
-                final NotificationEntry groupSummary =
-                        mGroupMembershipManager.getGroupSummary(topEntry);
-                if (groupSummary != null) {
-                    topEntry = groupSummary;
-                }
-            }
-            ExpandableNotificationRow topRow = topEntry.getRow();
-            int[] tmpArray = new int[2];
-            topRow.getLocationOnScreen(tmpArray);
-            int minX = tmpArray[0];
-            int maxX = tmpArray[0] + topRow.getWidth();
-            int height = topRow.getIntrinsicHeight();
-            final boolean stretchToTop = tmpArray[1] <= mHeadsUpInset;
-            mTouchableRegion.set(minX, stretchToTop ? 0 : tmpArray[1], maxX, tmpArray[1] + height);
-            return mTouchableRegion;
-        }
-    }
-
-    /**
-     * Decides whether a click is invalid for a notification, i.e it has not been shown long enough
-     * that a user might have consciously clicked on it.
-     *
-     * @param key the key of the touched notification
-     * @return whether the touch is invalid and should be discarded
-     */
-    @Override
-    public boolean shouldSwallowClick(@NonNull String key) {
-        BaseHeadsUpManager.HeadsUpEntry entry = getHeadsUpEntry(key);
-        return entry != null && mSystemClock.elapsedRealtime() < entry.mPostTime;
-    }
-
-    @Override
-    public void releaseAfterExpansion() {
-        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
-        onExpandingFinished();
-    }
-
-    public void onExpandingFinished() {
-        if (mReleaseOnExpandFinish) {
-            releaseAllImmediately();
-            mReleaseOnExpandFinish = false;
-        } else {
-            for (NotificationEntry entry: getAllEntries().toList()) {
-                entry.setSeenInShade(true);
-            }
-            for (NotificationEntry entry : mEntriesToRemoveAfterExpand) {
-                if (isHeadsUpEntry(entry.getKey())) {
-                    // Maybe the heads-up was removed already
-                    removeEntry(entry.getKey(), "onExpandingFinished");
-                }
-            }
-        }
-        mEntriesToRemoveAfterExpand.clear();
-    }
-
-    /**
-     * Sets the tracking-heads-up flag. If the flag is true, HeadsUpManager doesn't remove the entry
-     * from the list even after a Heads Up Notification is gone.
-     */
-    public void setTrackingHeadsUp(boolean trackingHeadsUp) {
-        mTrackingHeadsUp = trackingHeadsUp;
-    }
-
-    private void onShadeOrQsExpanded(Boolean isExpanded) {
-        if (isExpanded != mIsShadeOrQsExpanded) {
-            mIsShadeOrQsExpanded = isExpanded;
-            if (!SceneContainerFlag.isEnabled() && isExpanded) {
-                mHeadsUpAnimatingAway.setValue(false);
-            }
-        }
-    }
-
-    private void onQsExpanded(Boolean isQsExpanded) {
-        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
-        if (isQsExpanded != mIsQsExpanded) mIsQsExpanded = isQsExpanded;
-    }
-
-    /**
-     * Set that we are exiting the headsUp pinned mode, but some notifications might still be
-     * animating out. This is used to keep the touchable regions in a reasonable state.
-     */
-    @Override
-    public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
-        if (headsUpAnimatingAway != mHeadsUpAnimatingAway.getValue()) {
-            for (OnHeadsUpPhoneListenerChange listener : mHeadsUpPhoneListeners) {
-                listener.onHeadsUpAnimatingAwayStateChanged(headsUpAnimatingAway);
-            }
-            mHeadsUpAnimatingAway.setValue(headsUpAnimatingAway);
-        }
-    }
-
-    @Override
-    public void unpinAll(boolean userUnPinned) {
-        super.unpinAll(userUnPinned);
-    }
-
-    /**
-     * Notifies that a remote input textbox in notification gets active or inactive.
-     *
-     * @param entry             The entry of the target notification.
-     * @param remoteInputActive True to notify active, False to notify inactive.
-     */
-    public void setRemoteInputActive(
-            @NonNull NotificationEntry entry, boolean remoteInputActive) {
-        HeadsUpEntryPhone headsUpEntry = getHeadsUpEntryPhone(entry.getKey());
-        if (headsUpEntry != null && headsUpEntry.mRemoteInputActive != remoteInputActive) {
-            headsUpEntry.mRemoteInputActive = remoteInputActive;
-            if (ExpandHeadsUpOnInlineReply.isEnabled() && remoteInputActive) {
-                headsUpEntry.mRemoteInputActivatedAtLeastOnce = true;
-            }
-            if (remoteInputActive) {
-                headsUpEntry.cancelAutoRemovalCallbacks("setRemoteInputActive(true)");
-            } else {
-                headsUpEntry.updateEntry(false /* updatePostTime */, "setRemoteInputActive(false)");
-            }
-            onEntryUpdated(headsUpEntry);
-        }
-    }
-
-    /**
-     * Sets whether an entry's guts are exposed and therefore it should stick in the heads up
-     * area if it's pinned until it's hidden again.
-     */
-    public void setGutsShown(@NonNull NotificationEntry entry, boolean gutsShown) {
-        HeadsUpEntry headsUpEntry = getHeadsUpEntry(entry.getKey());
-        if (!(headsUpEntry instanceof HeadsUpEntryPhone)) return;
-        HeadsUpEntryPhone headsUpEntryPhone = (HeadsUpEntryPhone)headsUpEntry;
-        if (entry.isRowPinned() || !gutsShown) {
-            headsUpEntryPhone.setGutsShownPinned(gutsShown);
-        }
-    }
-
-    /**
-     * Extends the lifetime of the currently showing pulsing notification so that the pulse lasts
-     * longer.
-     */
-    public void extendHeadsUp() {
-        HeadsUpEntryPhone topEntry = getTopHeadsUpEntryPhone();
-        if (topEntry == null) {
-            return;
-        }
-        topEntry.extendPulse();
-    }
-
-    ///////////////////////////////////////////////////////////////////////////////////////////////
-    //  HeadsUpManager public methods overrides and overloads:
-
-    @Override
-    public boolean isTrackingHeadsUp() {
-        return mTrackingHeadsUp;
-    }
-
-    @Override
-    public void snooze() {
-        super.snooze();
-        mReleaseOnExpandFinish = true;
-    }
-
-    public void addSwipedOutNotification(@NonNull String key) {
-        mSwipedOutKeys.add(key);
-    }
-
-    @Override
-    public boolean removeNotification(@NonNull String key, boolean releaseImmediately,
-            boolean animate, @NonNull String reason) {
-        if (animate) {
-            return removeNotification(key, releaseImmediately,
-                    "removeNotification(animate: true), reason: " + reason);
-        } else {
-            mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
-            final boolean removed = removeNotification(key, releaseImmediately,
-                    "removeNotification(animate: false), reason: " + reason);
-            mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(true);
-            return removed;
-        }
-    }
-
-    ///////////////////////////////////////////////////////////////////////////////////////////////
-    //  Dumpable overrides:
-
-    @Override
-    public void dump(PrintWriter pw, String[] args) {
-        pw.println("HeadsUpManagerPhone state:");
-        dumpInternal(pw, args);
-    }
-
-    ///////////////////////////////////////////////////////////////////////////////////////////////
-    //  OnReorderingAllowedListener:
-
-    private final OnReorderingAllowedListener mOnReorderingAllowedListener = () -> {
-        if (NotificationThrottleHun.isEnabled()) {
-            mAvalancheController.setEnableAtRuntime(true);
-            if (mEntriesToRemoveWhenReorderingAllowed.isEmpty()) {
-                return;
-            }
-        }
-        mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
-        for (NotificationEntry entry : mEntriesToRemoveWhenReorderingAllowed) {
-            if (entry != null && isHeadsUpEntry(entry.getKey())) {
-                // Maybe the heads-up was removed already
-                removeEntry(entry.getKey(), "mOnReorderingAllowedListener");
-            }
-        }
-        mEntriesToRemoveWhenReorderingAllowed.clear();
-        mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(true);
-    };
-
-    private final OnReorderingBannedListener mOnReorderingBannedListener = () -> {
-        if (mAvalancheController != null) {
-            // In open shade the first HUN is pinned, and visual stability logic prevents us from
-            // unpinning this first HUN as long as the shade remains open. AvalancheController only
-            // shows the next HUN when the currently showing HUN is unpinned, so we must disable
-            // throttling here so that the incoming HUN stream is not forever paused. This is reset
-            // when reorder becomes allowed.
-            mAvalancheController.setEnableAtRuntime(false);
-
-            // Note that we cannot do the above when
-            // 1) The remove runnable runs because its delay means it may not run before shade close
-            // 2) Reordering is allowed again (when shade closes) because the HUN appear animation
-            // will have started by then
-        }
-    };
-
-    ///////////////////////////////////////////////////////////////////////////////////////////////
-    //  HeadsUpManager utility (protected) methods overrides:
-
-    @NonNull
-    @Override
-    protected HeadsUpEntry createHeadsUpEntry(NotificationEntry entry) {
-        if (NotificationThrottleHun.isEnabled()) {
-            return new HeadsUpEntryPhone(entry);
-        } else {
-            HeadsUpEntryPhone headsUpEntry = mEntryPool.acquire();
-            headsUpEntry.setEntry(entry);
-            return headsUpEntry;
-        }
-    }
-
-    @Override
-    protected void onEntryAdded(HeadsUpEntry headsUpEntry) {
-        super.onEntryAdded(headsUpEntry);
-        updateTopHeadsUpFlow();
-        updateHeadsUpFlow();
-    }
-
-    @Override
-    protected void onEntryUpdated(HeadsUpEntry headsUpEntry) {
-        super.onEntryUpdated(headsUpEntry);
-        // no need to update the list here
-        updateTopHeadsUpFlow();
-    }
-
-    @Override
-    protected void onEntryRemoved(HeadsUpEntry headsUpEntry) {
-        super.onEntryRemoved(headsUpEntry);
-        if (!NotificationThrottleHun.isEnabled()) {
-            mEntryPool.release((HeadsUpEntryPhone) headsUpEntry);
-        }
-        updateTopHeadsUpFlow();
-        updateHeadsUpFlow();
-        if (NotificationThrottleHun.isEnabled()) {
-            if (headsUpEntry.mEntry != null) {
-                if (mEntriesToRemoveWhenReorderingAllowed.contains(headsUpEntry.mEntry)) {
-                    mEntriesToRemoveWhenReorderingAllowed.remove(headsUpEntry.mEntry);
-                }
-            }
-        }
-    }
-
-    private void updateTopHeadsUpFlow() {
-        mTopHeadsUpRow.setValue((HeadsUpRowRepository) getTopHeadsUpEntry());
-    }
-
-    private void updateHeadsUpFlow() {
-        mHeadsUpNotificationRows.setValue(new HashSet<>(getHeadsUpEntryPhoneMap().values()));
-    }
-
-    @Override
-    protected boolean shouldHeadsUpBecomePinned(NotificationEntry entry) {
-        boolean pin = mStatusBarState == StatusBarState.SHADE && !mIsShadeOrQsExpanded;
-        if (SceneContainerFlag.isEnabled()) {
-            pin |= mIsQsExpanded;
-        }
-        if (mBypassController.getBypassEnabled()) {
-            pin |= mStatusBarState == StatusBarState.KEYGUARD;
-        }
-        return pin || super.shouldHeadsUpBecomePinned(entry);
-    }
-
-    @Override
-    protected void dumpInternal(PrintWriter pw, String[] args) {
-        super.dumpInternal(pw, args);
-        pw.print("  mBarState=");
-        pw.println(mStatusBarState);
-        pw.print("  mTouchableRegion=");
-        pw.println(mTouchableRegion);
-    }
-
-    ///////////////////////////////////////////////////////////////////////////////////////////////
-    //  Private utility methods:
-
-    @NonNull
-    private ArrayMap<String, HeadsUpEntryPhone> getHeadsUpEntryPhoneMap() {
-        //noinspection unchecked
-        return (ArrayMap<String, HeadsUpEntryPhone>) ((ArrayMap) mHeadsUpEntryMap);
-    }
-
-    @Nullable
-    private HeadsUpEntryPhone getHeadsUpEntryPhone(@NonNull String key) {
-        return (HeadsUpEntryPhone) mHeadsUpEntryMap.get(key);
-    }
-
-    @Nullable
-    private HeadsUpEntryPhone getTopHeadsUpEntryPhone() {
-        if (SceneContainerFlag.isEnabled()) {
-            return (HeadsUpEntryPhone) mTopHeadsUpRow.getValue();
-        } else {
-            return (HeadsUpEntryPhone) getTopHeadsUpEntry();
-        }
-    }
-
-    @Override
-    public boolean canRemoveImmediately(@NonNull String key) {
-        if (mSwipedOutKeys.contains(key)) {
-            // We always instantly dismiss views being manually swiped out.
-            mSwipedOutKeys.remove(key);
-            return true;
-        }
-
-        HeadsUpEntryPhone headsUpEntry = getHeadsUpEntryPhone(key);
-        HeadsUpEntryPhone topEntry = getTopHeadsUpEntryPhone();
-
-        return headsUpEntry == null || headsUpEntry != topEntry || super.canRemoveImmediately(key);
-    }
-
-    @Override
-    @NonNull
-    public Flow<HeadsUpRowRepository> getTopHeadsUpRow() {
-        return mTopHeadsUpRow;
-    }
-
-    @Override
-    @NonNull
-    public Flow<Set<HeadsUpRowRepository>> getActiveHeadsUpRows() {
-        return mHeadsUpNotificationRows;
-    }
-
-    @Override
-    @NonNull
-    public StateFlow<Boolean> isHeadsUpAnimatingAway() {
-        return mHeadsUpAnimatingAway;
-    }
-
-    @Override
-    public boolean isHeadsUpAnimatingAwayValue() {
-        return mHeadsUpAnimatingAway.getValue();
-    }
-
-    ///////////////////////////////////////////////////////////////////////////////////////////////
-    //  HeadsUpEntryPhone:
-
-    protected class HeadsUpEntryPhone extends BaseHeadsUpManager.HeadsUpEntry implements
-            HeadsUpRowRepository {
-
-        private boolean mGutsShownPinned;
-        private final MutableStateFlow<Boolean> mIsPinned = StateFlowKt.MutableStateFlow(false);
-
-        /**
-         * If the time this entry has been on was extended
-         */
-        private boolean extended;
-
-        @Override
-        public boolean isSticky() {
-            return super.isSticky() || mGutsShownPinned;
-        }
-
-        public HeadsUpEntryPhone() {
-            super();
-        }
-
-        public HeadsUpEntryPhone(NotificationEntry entry) {
-            super(entry);
-        }
-
-        @Override
-        @NonNull
-        public String getKey() {
-            return requireEntry().getKey();
-        }
-
-        @Override
-        @NonNull
-        public StateFlow<Boolean> isPinned() {
-            return mIsPinned;
-        }
-
-        @Override
-        protected void setRowPinned(boolean pinned) {
-            // TODO(b/327624082): replace this super call with a ViewBinder
-            super.setRowPinned(pinned);
-            mIsPinned.setValue(pinned);
-        }
-
-        @Override
-        protected void setEntry(@androidx.annotation.NonNull NotificationEntry entry,
-                @androidx.annotation.Nullable Runnable removeRunnable) {
-            super.setEntry(entry, removeRunnable);
-
-            if (NotificationThrottleHun.isEnabled()) {
-                mEntriesToRemoveWhenReorderingAllowed.add(entry);
-                if (!mVisualStabilityProvider.isReorderingAllowed()) {
-                    entry.setSeenInShade(true);
-                }
-            }
-        }
-
-        @Override
-        protected Runnable createRemoveRunnable(NotificationEntry entry) {
-            return () -> {
-                if (!NotificationThrottleHun.isEnabled()
-                        && !mVisualStabilityProvider.isReorderingAllowed()
-                        // We don't want to allow reordering while pulsing, but headsup need to
-                        // time out anyway
-                        && !entry.showingPulsing()) {
-                    mEntriesToRemoveWhenReorderingAllowed.add(entry);
-                    mVisualStabilityProvider.addTemporaryReorderingAllowedListener(
-                            mOnReorderingAllowedListener);
-                } else if (mTrackingHeadsUp) {
-                    mEntriesToRemoveAfterExpand.add(entry);
-                    mLogger.logRemoveEntryAfterExpand(entry);
-                } else if (mVisualStabilityProvider.isReorderingAllowed()
-                        || entry.showingPulsing()) {
-                    removeEntry(entry.getKey(), "createRemoveRunnable");
-                }
-            };
-        }
-
-        @Override
-        public void updateEntry(boolean updatePostTime, String reason) {
-            super.updateEntry(updatePostTime, reason);
-
-            if (mEntriesToRemoveAfterExpand.contains(mEntry)) {
-                mEntriesToRemoveAfterExpand.remove(mEntry);
-            }
-            if (!NotificationThrottleHun.isEnabled()) {
-                if (mEntriesToRemoveWhenReorderingAllowed.contains(mEntry)) {
-                    mEntriesToRemoveWhenReorderingAllowed.remove(mEntry);
-                }
-            }
-        }
-
-        @Override
-        public void setExpanded(boolean expanded) {
-            if (this.mExpanded == expanded) {
-                return;
-            }
-
-            this.mExpanded = expanded;
-            if (expanded) {
-                cancelAutoRemovalCallbacks("setExpanded(true)");
-            } else {
-                updateEntry(false /* updatePostTime */, "setExpanded(false)");
-            }
-        }
-
-        public void setGutsShownPinned(boolean gutsShownPinned) {
-            if (mGutsShownPinned == gutsShownPinned) {
-                return;
-            }
-
-            mGutsShownPinned = gutsShownPinned;
-            if (gutsShownPinned) {
-                cancelAutoRemovalCallbacks("setGutsShownPinned(true)");
-            } else {
-                updateEntry(false /* updatePostTime */, "setGutsShownPinned(false)");
-            }
-        }
-
-        @Override
-        public void reset() {
-            super.reset();
-            mGutsShownPinned = false;
-            extended = false;
-        }
-
-        private void extendPulse() {
-            if (!extended) {
-                extended = true;
-                updateEntry(false, "extendPulse()");
-            }
-        }
-
-        @Override
-        protected long calculateFinishTime() {
-            return super.calculateFinishTime() + (extended ? mExtensionTime : 0);
-        }
-
-        @Override
-        @NonNull
-        public Object getElementKey() {
-            return requireEntry().getRow();
-        }
-
-        private NotificationEntry requireEntry() {
-            /* check if */ SceneContainerFlag.isUnexpectedlyInLegacyMode();
-            return Objects.requireNonNull(mEntry);
-        }
-    }
-
-    private final StateListener mStatusBarStateListener = new StateListener() {
-        @Override
-        public void onStateChanged(int newState) {
-            boolean wasKeyguard = mStatusBarState == StatusBarState.KEYGUARD;
-            boolean isKeyguard = newState == StatusBarState.KEYGUARD;
-            mStatusBarState = newState;
-
-            if (wasKeyguard && !isKeyguard && mBypassController.getBypassEnabled()) {
-                ArrayList<String> keysToRemove = new ArrayList<>();
-                for (HeadsUpEntry entry : getHeadsUpEntryList()) {
-                    if (entry.mEntry != null && entry.mEntry.isBubble() && !entry.isSticky()) {
-                        keysToRemove.add(entry.mEntry.getKey());
-                    }
-                }
-                for (String key : keysToRemove) {
-                    removeEntry(key, "mStatusBarStateListener");
-                }
-            }
-        }
-
-        @Override
-        public void onDozingChanged(boolean isDozing) {
-            if (!isDozing) {
-                // Let's make sure all huns we got while dozing time out within the normal timeout
-                // duration. Otherwise they could get stuck for a very long time
-                for (HeadsUpEntry entry : getHeadsUpEntryList()) {
-                    entry.updateEntry(true /* updatePostTime */, "onDozingChanged(false)");
-                }
-            }
-        }
-    };
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
index 63c9e8b..cf4fb25 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
@@ -16,16 +16,11 @@
 package com.android.systemui.statusbar.notification.data
 
 import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository
-import com.android.systemui.statusbar.notification.HeadsUpManagerPhone
+import com.android.systemui.statusbar.policy.BaseHeadsUpManager
 import dagger.Binds
 import dagger.Module
 
-@Module(
-    includes =
-        [
-            NotificationSettingsRepositoryModule::class,
-        ]
-)
+@Module(includes = [NotificationSettingsRepositoryModule::class])
 interface NotificationDataLayerModule {
-    @Binds fun bindHeadsUpNotificationRepository(impl: HeadsUpManagerPhone): HeadsUpRepository
+    @Binds fun bindHeadsUpNotificationRepository(impl: BaseHeadsUpManager): HeadsUpRepository
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 80c8e8b..db29493 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -171,14 +171,12 @@
 import com.android.systemui.shade.NotificationShadeWindowViewController;
 import com.android.systemui.shade.QuickSettingsController;
 import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shade.ShadeExpandsOnStatusBarLongPress;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.shade.ShadeExpansionListener;
 import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.shade.ShadeLogger;
 import com.android.systemui.shade.ShadeSurface;
 import com.android.systemui.shade.ShadeViewController;
-import com.android.systemui.shade.StatusBarLongPressGestureDetector;
 import com.android.systemui.shared.recents.utilities.Utilities;
 import com.android.systemui.shared.statusbar.phone.BarTransitions;
 import com.android.systemui.statusbar.AutoHideUiElement;
@@ -368,7 +366,6 @@
 
     private PhoneStatusBarViewController mPhoneStatusBarViewController;
     private PhoneStatusBarTransitions mStatusBarTransitions;
-    private final Provider<StatusBarLongPressGestureDetector> mStatusBarLongPressGestureDetector;
     private final AuthRippleController mAuthRippleController;
     @WindowVisibleState private int mStatusBarWindowState = WINDOW_STATE_SHOWING;
     private final NotificationShadeWindowController mNotificationShadeWindowController;
@@ -674,7 +671,6 @@
             ShadeController shadeController,
             WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
-            Provider<StatusBarLongPressGestureDetector> statusBarLongPressGestureDetector,
             ViewMediatorCallback viewMediatorCallback,
             InitController initController,
             @Named(TIME_TICK_HANDLER_NAME) Handler timeTickHandler,
@@ -782,7 +778,6 @@
         mShadeController = shadeController;
         mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
-        mStatusBarLongPressGestureDetector = statusBarLongPressGestureDetector;
         mKeyguardViewMediatorCallback = viewMediatorCallback;
         mInitController = initController;
         mPluginDependencyProvider = pluginDependencyProvider;
@@ -1532,11 +1527,6 @@
                 // to touch outside the customizer to close it, such as on the status or nav bar.
                 mShadeController.onStatusBarTouch(event);
             }
-            if (ShadeExpandsOnStatusBarLongPress.isEnabled()
-                    && mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
-                mStatusBarLongPressGestureDetector.get().handleTouch(event);
-            }
-
             return getNotificationShadeWindowView().onTouchEvent(event);
         };
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpModule.kt
index 7919c84..83551e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpModule.kt
@@ -17,12 +17,12 @@
 package com.android.systemui.statusbar.phone
 
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.notification.HeadsUpManagerPhone
+import com.android.systemui.statusbar.policy.BaseHeadsUpManager
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import dagger.Binds
 import dagger.Module
 
 @Module
 interface HeadsUpModule {
-    @Binds @SysUISingleton fun bindsHeadsUpManager(hum: HeadsUpManagerPhone): HeadsUpManager
+    @Binds @SysUISingleton fun bindsHeadsUpManager(hum: BaseHeadsUpManager): HeadsUpManager
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 176dd8d..91c43dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -39,8 +39,8 @@
 import com.android.systemui.Flags;
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.res.R;
+import com.android.systemui.shade.LongPressGestureDetector;
 import com.android.systemui.shade.ShadeExpandsOnStatusBarLongPress;
-import com.android.systemui.shade.StatusBarLongPressGestureDetector;
 import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
 import com.android.systemui.statusbar.window.StatusBarWindowControllerStore;
 import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder;
@@ -69,7 +69,7 @@
     private InsetsFetcher mInsetsFetcher;
     private int mDensity;
     private float mFontScale;
-    private StatusBarLongPressGestureDetector mStatusBarLongPressGestureDetector;
+    private LongPressGestureDetector mLongPressGestureDetector;
 
     /**
      * Draw this many pixels into the left/right side of the cutout to optimally use the space
@@ -81,10 +81,9 @@
         mStatusBarWindowControllerStore = Dependency.get(StatusBarWindowControllerStore.class);
     }
 
-    void setLongPressGestureDetector(
-            StatusBarLongPressGestureDetector statusBarLongPressGestureDetector) {
+    void setLongPressGestureDetector(LongPressGestureDetector longPressGestureDetector) {
         if (ShadeExpandsOnStatusBarLongPress.isEnabled()) {
-            mStatusBarLongPressGestureDetector = statusBarLongPressGestureDetector;
+            mLongPressGestureDetector = longPressGestureDetector;
         }
     }
 
@@ -208,9 +207,8 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent event) {
-        if (ShadeExpandsOnStatusBarLongPress.isEnabled()
-                && mStatusBarLongPressGestureDetector != null) {
-            mStatusBarLongPressGestureDetector.handleTouch(event);
+        if (ShadeExpandsOnStatusBarLongPress.isEnabled() && mLongPressGestureDetector != null) {
+            mLongPressGestureDetector.handleTouch(event);
         }
         if (mTouchEventHandler == null) {
             Log.w(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index 16e023c..a94db49 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -34,11 +34,11 @@
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.ui.view.WindowRootView
+import com.android.systemui.shade.LongPressGestureDetector
 import com.android.systemui.shade.ShadeController
 import com.android.systemui.shade.ShadeExpandsOnStatusBarLongPress
 import com.android.systemui.shade.ShadeLogger
 import com.android.systemui.shade.ShadeViewController
-import com.android.systemui.shade.StatusBarLongPressGestureDetector
 import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
 import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
 import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
@@ -69,7 +69,7 @@
     private val shadeController: ShadeController,
     private val shadeViewController: ShadeViewController,
     private val panelExpansionInteractor: PanelExpansionInteractor,
-    private val statusBarLongPressGestureDetector: Provider<StatusBarLongPressGestureDetector>,
+    private val longPressGestureDetector: Provider<LongPressGestureDetector>,
     private val windowRootView: Provider<WindowRootView>,
     private val shadeLogger: ShadeLogger,
     private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
@@ -119,7 +119,7 @@
         addCursorSupportToIconContainers()
 
         if (ShadeExpandsOnStatusBarLongPress.isEnabled) {
-            mView.setLongPressGestureDetector(statusBarLongPressGestureDetector.get())
+            mView.setLongPressGestureDetector(longPressGestureDetector.get())
         }
 
         progressProvider?.setReadyToHandleTransition(true)
@@ -336,7 +336,7 @@
         private val shadeController: ShadeController,
         private val shadeViewController: ShadeViewController,
         private val panelExpansionInteractor: PanelExpansionInteractor,
-        private val statusBarLongPressGestureDetector: Provider<StatusBarLongPressGestureDetector>,
+        private val longPressGestureDetector: Provider<LongPressGestureDetector>,
         private val windowRootView: Provider<WindowRootView>,
         private val shadeLogger: ShadeLogger,
         private val viewUtil: ViewUtil,
@@ -361,7 +361,7 @@
                 shadeController,
                 shadeViewController,
                 panelExpansionInteractor,
-                statusBarLongPressGestureDetector,
+                longPressGestureDetector,
                 windowRootView,
                 shadeLogger,
                 statusBarMoveFromCenterAnimationController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
index c7b6be3..a1b56d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
@@ -112,8 +112,7 @@
             selectedUserContext
                 .flatMapLatest { currentContext
                     -> // flatMapLatest because when selectedUserContext emits a new value, we want
-                    // to
-                    // re-create a whole flow
+                    // to re-create a whole flow
                     callbackFlow {
                             val callback =
                                 object : WifiPickerTracker.WifiPickerTrackerCallback {
@@ -132,20 +131,16 @@
                                                 .map { it.toWifiNetworkModel() }
 
                                         // [WifiPickerTracker.connectedWifiEntry] will return the
-                                        // same
-                                        // instance but with updated internals. For example, when
-                                        // its
-                                        // validation status changes from false to true, the same
-                                        // instance is re-used but with the validated field updated.
+                                        // same instance but with updated internals. For example,
+                                        // when its validation status changes from false to true,
+                                        // the same instance is re-used but with the validated
+                                        // field updated.
                                         //
                                         // Because it's the same instance, the flow won't re-emit
-                                        // the
-                                        // value (even though the internals have changed). So, we
-                                        // need
-                                        // to transform it into our internal model immediately.
-                                        // [toWifiNetworkModel] always returns a new instance, so
-                                        // the
-                                        // flow is guaranteed to emit.
+                                        // the value (even though the internals have changed). So,
+                                        // we need to transform it into our internal model
+                                        // immediately. [toWifiNetworkModel] always returns a new
+                                        // instance, so the flow is guaranteed to emit.
                                         send(
                                             newPrimaryNetwork =
                                                 connectedEntry?.toPrimaryWifiNetworkModel()
@@ -235,20 +230,15 @@
                                             .map { it.toWifiNetworkModel() }
 
                                     // [WifiPickerTracker.connectedWifiEntry] will return the same
-                                    // instance
-                                    // but with updated internals. For example, when its validation
-                                    // status
-                                    // changes from false to true, the same instance is re-used but
-                                    // with the
-                                    // validated field updated.
+                                    // instance but with updated internals. For example, when its
+                                    // validation status changes from false to true, the same
+                                    // instance is re-used but with the validated field updated.
                                     //
                                     // Because it's the same instance, the flow won't re-emit the
-                                    // value
-                                    // (even though the internals have changed). So, we need to
-                                    // transform it
-                                    // into our internal model immediately. [toWifiNetworkModel]
-                                    // always
-                                    // returns a new instance, so the flow is guaranteed to emit.
+                                    // value (even though the internals have changed). So, we need
+                                    // to transform it into our internal model immediately.
+                                    // [toWifiNetworkModel] always returns a new instance, so the
+                                    // flow is guaranteed to emit.
                                     send(
                                         newPrimaryNetwork =
                                             connectedEntry?.toPrimaryWifiNetworkModel()
@@ -292,12 +282,10 @@
                                 .create(applicationContext, lifecycle, callback, "WifiRepository")
                                 .apply {
                                     // By default, [WifiPickerTracker] will scan to see all
-                                    // available wifi
-                                    // networks in the area. Because SysUI only needs to display the
-                                    // **connected** network, we don't need scans to be running (and
-                                    // in fact,
-                                    // running scans is costly and should be avoided whenever
-                                    // possible).
+                                    // available wifi networks in the area. Because SysUI only
+                                    // needs to display the **connected** network, we don't
+                                    // need scans to be running (and in fact, running scans is
+                                    // costly and should be avoided whenever possible).
                                     this?.disableScanning()
                                 }
                         // The lifecycle must be STARTED in order for the callback to receive
@@ -382,16 +370,11 @@
 
     private fun MergedCarrierEntry.convertCarrierMergedToModel(): WifiNetworkModel {
         // WifiEntry instance values aren't guaranteed to be stable between method calls
-        // because
-        // WifiPickerTracker is continuously updating the same object. Save the level in a
-        // local
-        // variable so that checking the level validity here guarantees that the level will
-        // still be
-        // valid when we create the `WifiNetworkModel.Active` instance later. Otherwise, the
-        // level
-        // could be valid here but become invalid later, and `WifiNetworkModel.Active` will
-        // throw
-        // an exception. See b/362384551.
+        // because WifiPickerTracker is continuously updating the same object. Save the
+        // level in a local variable so that checking the level validity here guarantees
+        // that the level will still be valid when we create the `WifiNetworkModel.Active`
+        // instance later. Otherwise, the level could be valid here but become invalid
+        // later, and `WifiNetworkModel.Active` will throw an exception. See b/362384551.
 
         return WifiNetworkModel.CarrierMerged.of(
             subscriptionId = this.subscriptionId,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
index 4284c19..f6f567f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -16,33 +16,48 @@
 
 package com.android.systemui.statusbar.policy;
 
-import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Notification;
 import android.content.Context;
 import android.content.res.Resources;
 import android.database.ContentObserver;
+import android.graphics.Region;
 import android.os.Handler;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.Pools;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
+import com.android.internal.policy.SystemBarUtils;
 import com.android.systemui.EventLogTags;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
+import com.android.systemui.shade.domain.interactor.ShadeInteractor;
+import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
+import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener;
+import com.android.systemui.statusbar.notification.collection.provider.OnReorderingBannedListener;
+import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
+import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository;
+import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRepository;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
 import com.android.systemui.statusbar.phone.ExpandHeadsUpOnInlineReply;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.util.ListenerSet;
 import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.settings.GlobalSettings;
 import com.android.systemui.util.time.SystemClock;
 
@@ -50,14 +65,27 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.Stack;
 import java.util.stream.Stream;
 
+import javax.inject.Inject;
+
+import kotlinx.coroutines.flow.Flow;
+import kotlinx.coroutines.flow.MutableStateFlow;
+import kotlinx.coroutines.flow.StateFlow;
+import kotlinx.coroutines.flow.StateFlowKt;
+
 /**
  * A manager which handles heads up notifications which is a special mode where
  * they simply peek from the top of the screen.
  */
-public abstract class BaseHeadsUpManager implements HeadsUpManager {
+@SysUISingleton
+public class BaseHeadsUpManager
+        implements HeadsUpManager, HeadsUpRepository, OnHeadsUpChangedListener {
     private static final String TAG = "BaseHeadsUpManager";
     private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms";
 
@@ -74,7 +102,11 @@
     private final AccessibilityManagerWrapper mAccessibilityMgr;
 
     private final UiEventLogger mUiEventLogger;
-    private final AvalancheController mAvalancheController;
+    private AvalancheController mAvalancheController;
+    private final KeyguardBypassController mBypassController;
+    private final GroupMembershipManager mGroupMembershipManager;
+    private final List<OnHeadsUpPhoneListenerChange> mHeadsUpPhoneListeners = new ArrayList<>();
+    private final VisualStabilityProvider mVisualStabilityProvider;
 
     protected final SystemClock mSystemClock;
     protected final ArrayMap<String, HeadsUpEntry> mHeadsUpEntryMap = new ArrayMap<>();
@@ -84,6 +116,53 @@
     protected int mAutoDismissTime;
     protected DelayableExecutor mExecutor;
 
+    @VisibleForTesting
+    public final int mExtensionTime;
+
+    // TODO(b/328393698) move the topHeadsUpRow logic to an interactor
+    private final MutableStateFlow<HeadsUpRowRepository> mTopHeadsUpRow =
+            StateFlowKt.MutableStateFlow(null);
+    private final MutableStateFlow<Set<HeadsUpRowRepository>> mHeadsUpNotificationRows =
+            StateFlowKt.MutableStateFlow(new HashSet<>());
+    private final MutableStateFlow<Boolean> mHeadsUpAnimatingAway =
+            StateFlowKt.MutableStateFlow(false);
+    private final HashSet<String> mSwipedOutKeys = new HashSet<>();
+    private final HashSet<NotificationEntry> mEntriesToRemoveAfterExpand = new HashSet<>();
+    @VisibleForTesting
+    public final ArraySet<NotificationEntry> mEntriesToRemoveWhenReorderingAllowed
+            = new ArraySet<>();
+
+    private boolean mReleaseOnExpandFinish;
+    private boolean mTrackingHeadsUp;
+    private boolean mIsShadeOrQsExpanded;
+    private boolean mIsQsExpanded;
+    private int mStatusBarState;
+    private AnimationStateHandler mAnimationStateHandler;
+    private int mHeadsUpInset;
+
+    // Used for determining the region for touch interaction
+    private final Region mTouchableRegion = new Region();
+
+    private final Pools.Pool<HeadsUpEntry> mEntryPool = new Pools.Pool<>() {
+        private final Stack<HeadsUpEntry> mPoolObjects = new Stack<>();
+
+        @Override
+        public HeadsUpEntry acquire() {
+            NotificationThrottleHun.assertInLegacyMode();
+            if (!mPoolObjects.isEmpty()) {
+                return mPoolObjects.pop();
+            }
+            return new HeadsUpEntry();
+        }
+
+        @Override
+        public boolean release(@NonNull HeadsUpEntry instance) {
+            NotificationThrottleHun.assertInLegacyMode();
+            mPoolObjects.push(instance);
+            return true;
+        }
+    };
+
     /**
      * Enum entry for notification peek logged from this class.
      */
@@ -100,14 +179,23 @@
         }
     }
 
-    public BaseHeadsUpManager(@NonNull final Context context,
+    @Inject
+    public BaseHeadsUpManager(
+            @NonNull final Context context,
             HeadsUpManagerLogger logger,
+            StatusBarStateController statusBarStateController,
+            KeyguardBypassController bypassController,
+            GroupMembershipManager groupMembershipManager,
+            VisualStabilityProvider visualStabilityProvider,
+            ConfigurationController configurationController,
             @Main Handler handler,
             GlobalSettings globalSettings,
             SystemClock systemClock,
             @Main DelayableExecutor executor,
             AccessibilityManagerWrapper accessibilityManagerWrapper,
             UiEventLogger uiEventLogger,
+            JavaAdapter javaAdapter,
+            ShadeInteractor shadeInteractor,
             AvalancheController avalancheController) {
         mLogger = logger;
         mExecutor = executor;
@@ -117,12 +205,16 @@
         mUiEventLogger = uiEventLogger;
         mAvalancheController = avalancheController;
         mAvalancheController.setBaseEntryMapStr(this::getEntryMapStr);
+        mBypassController = bypassController;
+        mGroupMembershipManager = groupMembershipManager;
+        mVisualStabilityProvider = visualStabilityProvider;
         Resources resources = context.getResources();
         mMinimumDisplayTime = NotificationThrottleHun.isEnabled()
                 ? 500 : resources.getInteger(R.integer.heads_up_notification_minimum_time);
         mStickyForSomeTimeAutoDismissTime = resources.getInteger(
                 R.integer.sticky_heads_up_notification_time);
         mAutoDismissTime = resources.getInteger(R.integer.heads_up_notification_decay);
+        mExtensionTime = resources.getInteger(R.integer.ambient_notification_extension_time);
         mTouchAcceptanceDelay = resources.getInteger(R.integer.touch_acceptance_delay);
         mSnoozedPackages = new ArrayMap<>();
         int defaultSnoozeLengthMs =
@@ -145,11 +237,38 @@
                 globalSettings.getUriFor(SETTING_HEADS_UP_SNOOZE_LENGTH_MS),
                 /* notifyForDescendants = */ false,
                 settingsObserver);
+
+        statusBarStateController.addCallback(mStatusBarStateListener);
+        updateResources();
+        configurationController.addCallback(new ConfigurationController.ConfigurationListener() {
+            @Override
+            public void onDensityOrFontScaleChanged() {
+                updateResources();
+            }
+
+            @Override
+            public void onThemeChanged() {
+                updateResources();
+            }
+        });
+        javaAdapter.alwaysCollectFlow(shadeInteractor.isAnyExpanded(),
+                this::onShadeOrQsExpanded);
+        if (SceneContainerFlag.isEnabled()) {
+            javaAdapter.alwaysCollectFlow(shadeInteractor.isQsExpanded(),
+                    this::onQsExpanded);
+        }
+        if (NotificationThrottleHun.isEnabled()) {
+            mVisualStabilityProvider.addPersistentReorderingBannedListener(
+                    mOnReorderingBannedListener);
+            mVisualStabilityProvider.addPersistentReorderingAllowedListener(
+                    mOnReorderingAllowedListener);
+        }
     }
 
     /**
      * Adds an OnHeadUpChangedListener to observe events.
      */
+    @Override
     public void addListener(@NonNull OnHeadsUpChangedListener listener) {
         mListeners.addIfAbsent(listener);
     }
@@ -157,11 +276,31 @@
     /**
      * Removes the OnHeadUpChangedListener from the observer list.
      */
+    @Override
     public void removeListener(@NonNull OnHeadsUpChangedListener listener) {
         mListeners.remove(listener);
     }
 
     /**
+     * Add a listener to receive callbacks {@link #setHeadsUpAnimatingAway(boolean)}
+     */
+    @Override
+    public void addHeadsUpPhoneListener(@NonNull OnHeadsUpPhoneListenerChange listener) {
+        mHeadsUpPhoneListeners.add(listener);
+    }
+
+    @Override
+    public void setAnimationStateHandler(@NonNull AnimationStateHandler handler) {
+        mAnimationStateHandler = handler;
+    }
+
+    private void updateResources() {
+        Resources resources = mContext.getResources();
+        mHeadsUpInset = SystemBarUtils.getStatusBarHeight(mContext)
+                + resources.getDimensionPixelSize(R.dimen.heads_up_status_bar_padding);
+    }
+
+    /**
      * Called when posting a new notification that should appear on screen.
      * Adds the notification to be managed.
      * @param entry entry to show
@@ -188,14 +327,24 @@
         mAvalancheController.update(headsUpEntry, runnable, "showNotification");
     }
 
-    /**
-     * Try to remove the notification.  May not succeed if the notification has not been shown long
-     * enough and needs to be kept around.
-     * @param key the key of the notification to remove
-     * @param releaseImmediately force a remove regardless of earliest removal time
-     * @param reason reason for removing the notification
-     * @return true if notification is removed, false otherwise
-     */
+    @Override
+    public boolean removeNotification(
+            @NonNull String key,
+            boolean releaseImmediately,
+            boolean animate,
+            @NonNull String reason) {
+        if (animate) {
+            return removeNotification(key, releaseImmediately,
+                    "removeNotification(animate: true), reason: " + reason);
+        } else {
+            mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
+            final boolean removed = removeNotification(key, releaseImmediately,
+                    "removeNotification(animate: false), reason: " + reason);
+            mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(true);
+            return removed;
+        }
+    }
+
     @Override
     public boolean removeNotification(@NotNull String key, boolean releaseImmediately,
             @NonNull String reason) {
@@ -261,6 +410,42 @@
         }
     }
 
+    @Override
+    public void setTrackingHeadsUp(boolean trackingHeadsUp) {
+        mTrackingHeadsUp = trackingHeadsUp;
+    }
+
+    @Override
+    public boolean shouldSwallowClick(@NonNull String key) {
+        BaseHeadsUpManager.HeadsUpEntry entry = getHeadsUpEntry(key);
+        return entry != null && mSystemClock.elapsedRealtime() < entry.mPostTime;
+    }
+
+    @Override
+    public void releaseAfterExpansion() {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+        onExpandingFinished();
+    }
+
+    @Override
+    public void onExpandingFinished() {
+        if (mReleaseOnExpandFinish) {
+            releaseAllImmediately();
+            mReleaseOnExpandFinish = false;
+        } else {
+            for (NotificationEntry entry : getAllEntries().toList()) {
+                entry.setSeenInShade(true);
+            }
+            for (NotificationEntry entry : mEntriesToRemoveAfterExpand) {
+                if (isHeadsUpEntry(entry.getKey())) {
+                    // Maybe the heads-up was removed already
+                    removeEntry(entry.getKey(), "onExpandingFinished");
+                }
+            }
+        }
+        mEntriesToRemoveAfterExpand.clear();
+    }
+
     /**
      * Clears all managed notifications.
      */
@@ -339,10 +524,19 @@
         return 0;
     }
 
+    @VisibleForTesting
     protected boolean shouldHeadsUpBecomePinned(@NonNull NotificationEntry entry) {
-        if (entry == null) {
-            return false;
+        boolean pin = mStatusBarState == StatusBarState.SHADE && !mIsShadeOrQsExpanded;
+        if (SceneContainerFlag.isEnabled()) {
+            pin |= mIsQsExpanded;
         }
+        if (mBypassController.getBypassEnabled()) {
+            pin |= mStatusBarState == StatusBarState.KEYGUARD;
+        }
+        if (pin) {
+            return true;
+        }
+
         final HeadsUpEntry headsUpEntry = getHeadsUpEntry(entry.getKey());
         if (headsUpEntry == null) {
             // This should not happen since shouldHeadsUpBecomePinned is always called after adding
@@ -392,10 +586,6 @@
         }
     }
 
-    public @InflationFlag int getContentFlag() {
-        return FLAG_CONTENT_VIEW_HEADS_UP;
-    }
-
     /**
      * Manager-specific logic that should occur when an entry is added.
      * @param headsUpEntry entry added
@@ -410,6 +600,8 @@
         for (OnHeadsUpChangedListener listener : mListeners) {
             listener.onHeadsUpStateChanged(entry, true);
         }
+        updateTopHeadsUpFlow();
+        updateHeadsUpFlow();
     }
 
     /**
@@ -466,11 +658,60 @@
         for (OnHeadsUpChangedListener listener : mListeners) {
             listener.onHeadsUpStateChanged(entry, false);
         }
+        if (!NotificationThrottleHun.isEnabled()) {
+            mEntryPool.release(headsUpEntry);
+        }
+        updateTopHeadsUpFlow();
+        updateHeadsUpFlow();
+        if (NotificationThrottleHun.isEnabled()) {
+            if (headsUpEntry.mEntry != null) {
+                if (mEntriesToRemoveWhenReorderingAllowed.contains(headsUpEntry.mEntry)) {
+                    mEntriesToRemoveWhenReorderingAllowed.remove(headsUpEntry.mEntry);
+                }
+            }
+        }
+    }
+
+    private void updateTopHeadsUpFlow() {
+        mTopHeadsUpRow.setValue((HeadsUpRowRepository) getTopHeadsUpEntry());
+    }
+
+    private void updateHeadsUpFlow() {
+        mHeadsUpNotificationRows.setValue(new HashSet<>(getHeadsUpEntryPhoneMap().values()));
+    }
+
+    @Override
+    @NonNull
+    public Flow<HeadsUpRowRepository> getTopHeadsUpRow() {
+        return mTopHeadsUpRow;
+    }
+
+    @Override
+    @NonNull
+    public Flow<Set<HeadsUpRowRepository>> getActiveHeadsUpRows() {
+        return mHeadsUpNotificationRows;
+    }
+
+    @Override
+    @NonNull
+    public StateFlow<Boolean> isHeadsUpAnimatingAway() {
+        return mHeadsUpAnimatingAway;
+    }
+
+    @Override
+    public boolean isHeadsUpAnimatingAwayValue() {
+        return mHeadsUpAnimatingAway.getValue();
+    }
+
+    @NonNull
+    private ArrayMap<String, HeadsUpEntry> getHeadsUpEntryPhoneMap() {
+        return mHeadsUpEntryMap;
     }
 
     /**
      * Called to notify the listeners that the HUN animating away animation has ended.
      */
+    @Override
     public void onEntryAnimatingAwayEnded(@NonNull NotificationEntry entry) {
         for (OnHeadsUpChangedListener listener : mListeners) {
             listener.onHeadsUpAnimatingAwayEnded(entry);
@@ -484,6 +725,8 @@
      * @param headsUpEntry entry updated
      */
     protected void onEntryUpdated(HeadsUpEntry headsUpEntry) {
+        // no need to update the list here
+        updateTopHeadsUpFlow();
     }
 
     protected void updatePinnedMode() {
@@ -521,6 +764,7 @@
     /**
      * Snoozes all current Heads Up Notifications.
      */
+    @Override
     public void snooze() {
         List<String> keySet = new ArrayList<>(mHeadsUpEntryMap.keySet());
         keySet.addAll(mAvalancheController.getWaitingKeys());
@@ -534,6 +778,7 @@
             mLogger.logPackageSnoozed(snoozeKey);
             mSnoozedPackages.put(snoozeKey, mSystemClock.elapsedRealtime() + mSnoozeLengthMs);
         }
+        mReleaseOnExpandFinish = true;
     }
 
     @NonNull
@@ -541,6 +786,11 @@
         return user + "," + packageName;
     }
 
+    @Override
+    public void addSwipedOutNotification(@NonNull String key) {
+        mSwipedOutKeys.add(key);
+    }
+
     @Nullable
     protected HeadsUpEntry getHeadsUpEntry(@NonNull String key) {
         if (mHeadsUpEntryMap.containsKey(key)) {
@@ -597,6 +847,59 @@
     }
 
     @Override
+    public @Nullable Region getTouchableRegion() {
+        NotificationEntry topEntry = getTopEntry();
+
+        // This call could be made in an inconsistent state while the pinnedMode hasn't been
+        // updated yet, but callbacks leading out of the headsUp manager, querying it. Let's
+        // therefore also check if the topEntry is null.
+        if (!hasPinnedHeadsUp() || topEntry == null) {
+            return null;
+        } else {
+            if (topEntry.rowIsChildInGroup()) {
+                final NotificationEntry groupSummary =
+                        mGroupMembershipManager.getGroupSummary(topEntry);
+                if (groupSummary != null) {
+                    topEntry = groupSummary;
+                }
+            }
+            ExpandableNotificationRow topRow = topEntry.getRow();
+            int[] tmpArray = new int[2];
+            topRow.getLocationOnScreen(tmpArray);
+            int minX = tmpArray[0];
+            int maxX = tmpArray[0] + topRow.getWidth();
+            int height = topRow.getIntrinsicHeight();
+            final boolean stretchToTop = tmpArray[1] <= mHeadsUpInset;
+            mTouchableRegion.set(minX, stretchToTop ? 0 : tmpArray[1], maxX, tmpArray[1] + height);
+            return mTouchableRegion;
+        }
+    }
+
+    @Override
+    public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
+        if (headsUpAnimatingAway != mHeadsUpAnimatingAway.getValue()) {
+            for (OnHeadsUpPhoneListenerChange listener : mHeadsUpPhoneListeners) {
+                listener.onHeadsUpAnimatingAwayStateChanged(headsUpAnimatingAway);
+            }
+            mHeadsUpAnimatingAway.setValue(headsUpAnimatingAway);
+        }
+    }
+
+    private void onShadeOrQsExpanded(Boolean isExpanded) {
+        if (isExpanded != mIsShadeOrQsExpanded) {
+            mIsShadeOrQsExpanded = isExpanded;
+            if (!SceneContainerFlag.isEnabled() && isExpanded) {
+                mHeadsUpAnimatingAway.setValue(false);
+            }
+        }
+    }
+
+    private void onQsExpanded(Boolean isQsExpanded) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+        if (isQsExpanded != mIsQsExpanded) mIsQsExpanded = isQsExpanded;
+    }
+
+    @Override
     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("HeadsUpManager state:");
         dumpInternal(pw, args);
@@ -616,6 +919,10 @@
             pw.print("    "); pw.print(mSnoozedPackages.valueAt(i));
             pw.print(", "); pw.println(mSnoozedPackages.keyAt(i));
         }
+        pw.print("  mBarState=");
+        pw.println(mStatusBarState);
+        pw.print("  mTouchableRegion=");
+        pw.println(mTouchableRegion);
     }
 
     /**
@@ -639,6 +946,7 @@
      * Unpins all pinned Heads Up Notifications.
      * @param userUnPinned The unpinned action is trigger by user real operation.
      */
+    @Override
     public void unpinAll(boolean userUnPinned) {
         for (String key : mHeadsUpEntryMap.keySet()) {
             HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
@@ -662,13 +970,59 @@
         }
     }
 
-    /**
-     * Returns the value of the tracking-heads-up flag. See the doc of {@code setTrackingHeadsUp} as
-     * well.
-     */
+    @Override
+    public void setRemoteInputActive(
+            @NonNull NotificationEntry entry, boolean remoteInputActive) {
+        HeadsUpEntry headsUpEntry = getHeadsUpEntryPhone(entry.getKey());
+        if (headsUpEntry != null && headsUpEntry.mRemoteInputActive != remoteInputActive) {
+            headsUpEntry.mRemoteInputActive = remoteInputActive;
+            if (ExpandHeadsUpOnInlineReply.isEnabled() && remoteInputActive) {
+                headsUpEntry.mRemoteInputActivatedAtLeastOnce = true;
+            }
+            if (remoteInputActive) {
+                headsUpEntry.cancelAutoRemovalCallbacks("setRemoteInputActive(true)");
+            } else {
+                headsUpEntry.updateEntry(false /* updatePostTime */, "setRemoteInputActive(false)");
+            }
+            onEntryUpdated(headsUpEntry);
+        }
+    }
+
+    @Nullable
+    private HeadsUpEntry getHeadsUpEntryPhone(@NonNull String key) {
+        return mHeadsUpEntryMap.get(key);
+    }
+
+    @Override
+    public void setGutsShown(@NonNull NotificationEntry entry, boolean gutsShown) {
+        HeadsUpEntry headsUpEntry = getHeadsUpEntry(entry.getKey());
+        if (headsUpEntry == null) return;
+        if (entry.isRowPinned() || !gutsShown) {
+            headsUpEntry.setGutsShownPinned(gutsShown);
+        }
+    }
+
+    @Override
+    public void extendHeadsUp() {
+        HeadsUpEntry topEntry = getTopHeadsUpEntryPhone();
+        if (topEntry == null) {
+            return;
+        }
+        topEntry.extendPulse();
+    }
+
+    @Nullable
+    private HeadsUpEntry getTopHeadsUpEntryPhone() {
+        if (SceneContainerFlag.isEnabled()) {
+            return (HeadsUpEntry) mTopHeadsUpRow.getValue();
+        } else {
+            return getTopHeadsUpEntry();
+        }
+    }
+
+    @Override
     public boolean isTrackingHeadsUp() {
-        // Might be implemented in subclass.
-        return false;
+        return mTrackingHeadsUp;
     }
 
     /**
@@ -724,11 +1078,23 @@
      */
     @Override
     public boolean canRemoveImmediately(@NonNull String key) {
-        HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
-        if (headsUpEntry != null && headsUpEntry.mUserActionMayIndirectlyRemove) {
+        if (mSwipedOutKeys.contains(key)) {
+            // We always instantly dismiss views being manually swiped out.
+            mSwipedOutKeys.remove(key);
             return true;
         }
-        return headsUpEntry == null || headsUpEntry.wasShownLongEnough()
+
+        HeadsUpEntry headsUpEntry = getHeadsUpEntryPhone(key);
+        HeadsUpEntry topEntry = getTopHeadsUpEntryPhone();
+
+        if (headsUpEntry == null || headsUpEntry != topEntry) {
+            return true;
+        }
+
+        if (headsUpEntry.mUserActionMayIndirectlyRemove) {
+            return true;
+        }
+        return headsUpEntry.wasShownLongEnough()
                 || (headsUpEntry.mEntry != null && headsUpEntry.mEntry.isRowDismissed());
     }
 
@@ -747,7 +1113,13 @@
 
     @NonNull
     protected HeadsUpEntry createHeadsUpEntry(NotificationEntry entry) {
-        return new HeadsUpEntry(entry);
+        if (NotificationThrottleHun.isEnabled()) {
+            return new HeadsUpEntry(entry);
+        } else {
+            HeadsUpEntry headsUpEntry = mEntryPool.acquire();
+            headsUpEntry.setEntry(entry);
+            return headsUpEntry;
+        }
     }
 
     /**
@@ -763,12 +1135,79 @@
                 && Notification.CATEGORY_CALL.equals(n.category));
     }
 
+    private final OnReorderingAllowedListener mOnReorderingAllowedListener = () -> {
+        if (NotificationThrottleHun.isEnabled()) {
+            mAvalancheController.setEnableAtRuntime(true);
+            if (mEntriesToRemoveWhenReorderingAllowed.isEmpty()) {
+                return;
+            }
+        }
+        mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
+        for (NotificationEntry entry : mEntriesToRemoveWhenReorderingAllowed) {
+            if (entry != null && isHeadsUpEntry(entry.getKey())) {
+                // Maybe the heads-up was removed already
+                removeEntry(entry.getKey(), "mOnReorderingAllowedListener");
+            }
+        }
+        mEntriesToRemoveWhenReorderingAllowed.clear();
+        mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(true);
+    };
+
+    private final OnReorderingBannedListener mOnReorderingBannedListener = () -> {
+        if (mAvalancheController != null) {
+            // In open shade the first HUN is pinned, and visual stability logic prevents us from
+            // unpinning this first HUN as long as the shade remains open. AvalancheController only
+            // shows the next HUN when the currently showing HUN is unpinned, so we must disable
+            // throttling here so that the incoming HUN stream is not forever paused. This is reset
+            // when reorder becomes allowed.
+            mAvalancheController.setEnableAtRuntime(false);
+
+            // Note that we cannot do the above when
+            // 1) The remove runnable runs because its delay means it may not run before shade close
+            // 2) Reordering is allowed again (when shade closes) because the HUN appear animation
+            // will have started by then
+        }
+    };
+
+    private final StatusBarStateController.StateListener
+            mStatusBarStateListener = new StatusBarStateController.StateListener() {
+        @Override
+        public void onStateChanged(int newState) {
+            boolean wasKeyguard = mStatusBarState == StatusBarState.KEYGUARD;
+            boolean isKeyguard = newState == StatusBarState.KEYGUARD;
+            mStatusBarState = newState;
+
+            if (wasKeyguard && !isKeyguard && mBypassController.getBypassEnabled()) {
+                ArrayList<String> keysToRemove = new ArrayList<>();
+                for (HeadsUpEntry entry : getHeadsUpEntryList()) {
+                    if (entry.mEntry != null && entry.mEntry.isBubble() && !entry.isSticky()) {
+                        keysToRemove.add(entry.mEntry.getKey());
+                    }
+                }
+                for (String key : keysToRemove) {
+                    removeEntry(key, "mStatusBarStateListener");
+                }
+            }
+        }
+
+        @Override
+        public void onDozingChanged(boolean isDozing) {
+            if (!isDozing) {
+                // Let's make sure all huns we got while dozing time out within the normal timeout
+                // duration. Otherwise they could get stuck for a very long time
+                for (HeadsUpEntry entry : getHeadsUpEntryList()) {
+                    entry.updateEntry(true /* updatePostTime */, "onDozingChanged(false)");
+                }
+            }
+        }
+    };
+
     /**
      * This represents a notification and how long it is in a heads up mode. It also manages its
      * lifecycle automatically when created. This class is public because it is exposed by methods
      * of AvalancheController that take it as param.
      */
-    public class HeadsUpEntry implements Comparable<HeadsUpEntry> {
+    public class HeadsUpEntry implements Comparable<HeadsUpEntry>, HeadsUpRowRepository {
         public boolean mRemoteInputActivatedAtLeastOnce;
         public boolean mRemoteInputActive;
         public boolean mUserActionMayIndirectlyRemove;
@@ -784,6 +1223,14 @@
 
         @Nullable private Runnable mCancelRemoveRunnable;
 
+        private boolean mGutsShownPinned;
+        private final MutableStateFlow<Boolean> mIsPinned = StateFlowKt.MutableStateFlow(false);
+
+        /**
+         * If the time this entry has been on was extended
+         */
+        private boolean extended;
+
         public HeadsUpEntry() {
             NotificationThrottleHun.assertInLegacyMode();
         }
@@ -794,19 +1241,50 @@
             setEntry(entry, createRemoveRunnable(entry));
         }
 
+        @Override
+        @NonNull
+        public String getKey() {
+            return requireEntry().getKey();
+        }
+
+        @Override
+        @NonNull
+        public Object getElementKey() {
+            return requireEntry().getRow();
+        }
+
+        private NotificationEntry requireEntry() {
+            /* check if */ SceneContainerFlag.isUnexpectedlyInLegacyMode();
+            return Objects.requireNonNull(mEntry);
+        }
+
+        @Override
+        @NonNull
+        public StateFlow<Boolean> isPinned() {
+            return mIsPinned;
+        }
+
         /** Attach a NotificationEntry. */
         public void setEntry(@NonNull final NotificationEntry entry) {
             NotificationThrottleHun.assertInLegacyMode();
             setEntry(entry, createRemoveRunnable(entry));
         }
 
-        protected void setEntry(@NonNull final NotificationEntry entry,
+        protected void setEntry(
+                @NonNull final NotificationEntry entry,
                 @Nullable Runnable removeRunnable) {
             mEntry = entry;
             mRemoveRunnable = removeRunnable;
 
             mPostTime = calculatePostTime();
             updateEntry(true /* updatePostTime */, "setEntry");
+
+            if (NotificationThrottleHun.isEnabled()) {
+                mEntriesToRemoveWhenReorderingAllowed.add(entry);
+                if (!mVisualStabilityProvider.isReorderingAllowed()) {
+                    entry.setSeenInShade(true);
+                }
+            }
         }
 
         protected boolean isRowPinned() {
@@ -815,6 +1293,7 @@
 
         protected void setRowPinned(boolean pinned) {
             if (mEntry != null) mEntry.setRowPinned(pinned);
+            mIsPinned.setValue(pinned);
         }
 
         /**
@@ -870,6 +1349,22 @@
 
             // Notify the manager, that the posted time has changed.
             onEntryUpdated(this);
+
+            if (mEntriesToRemoveAfterExpand.contains(mEntry)) {
+                mEntriesToRemoveAfterExpand.remove(mEntry);
+            }
+            if (!NotificationThrottleHun.isEnabled()) {
+                if (mEntriesToRemoveWhenReorderingAllowed.contains(mEntry)) {
+                    mEntriesToRemoveWhenReorderingAllowed.remove(mEntry);
+                }
+            }
+        }
+
+        private void extendPulse() {
+            if (!extended) {
+                extended = true;
+                updateEntry(false, "extendPulse()");
+            }
         }
 
         /**
@@ -878,6 +1373,8 @@
          * @return true if the notification is sticky
          */
         public boolean isSticky() {
+            if (mGutsShownPinned) return true;
+
             if (mEntry == null) return false;
 
             if (ExpandHeadsUpOnInlineReply.isEnabled()) {
@@ -989,7 +1486,29 @@
         }
 
         public void setExpanded(boolean expanded) {
+            if (this.mExpanded == expanded) {
+                return;
+            }
+
             this.mExpanded = expanded;
+            if (expanded) {
+                cancelAutoRemovalCallbacks("setExpanded(true)");
+            } else {
+                updateEntry(false /* updatePostTime */, "setExpanded(false)");
+            }
+        }
+
+        public void setGutsShownPinned(boolean gutsShownPinned) {
+            if (mGutsShownPinned == gutsShownPinned) {
+                return;
+            }
+
+            mGutsShownPinned = gutsShownPinned;
+            if (gutsShownPinned) {
+                cancelAutoRemovalCallbacks("setGutsShownPinned(true)");
+            } else {
+                updateEntry(false /* updatePostTime */, "setGutsShownPinned(false)");
+            }
         }
 
         public void reset() {
@@ -999,6 +1518,8 @@
             mRemoveRunnable = null;
             mExpanded = false;
             mRemoteInputActive = false;
+            mGutsShownPinned = false;
+            extended = false;
         }
 
         /**
@@ -1074,7 +1595,23 @@
 
         /** Creates a runnable to remove this notification from the alerting entries. */
         protected Runnable createRemoveRunnable(NotificationEntry entry) {
-            return () -> removeEntry(entry.getKey(), "createRemoveRunnable");
+            return () -> {
+                if (!NotificationThrottleHun.isEnabled()
+                        && !mVisualStabilityProvider.isReorderingAllowed()
+                        // We don't want to allow reordering while pulsing, but headsup need to
+                        // time out anyway
+                        && !entry.showingPulsing()) {
+                    mEntriesToRemoveWhenReorderingAllowed.add(entry);
+                    mVisualStabilityProvider.addTemporaryReorderingAllowedListener(
+                            mOnReorderingAllowedListener);
+                } else if (mTrackingHeadsUp) {
+                    mEntriesToRemoveAfterExpand.add(entry);
+                    mLogger.logRemoveEntryAfterExpand(entry);
+                } else if (mVisualStabilityProvider.isReorderingAllowed()
+                        || entry.showingPulsing()) {
+                    removeEntry(entry.getKey(), "createRemoveRunnable");
+                }
+            };
         }
 
         /**
@@ -1098,7 +1635,7 @@
                 requestedTimeOutMs = mAvalancheController.getDurationMs(this, mAutoDismissTime);
             }
             final long duration = getRecommendedHeadsUpTimeoutMs(requestedTimeOutMs);
-            return mPostTime + duration;
+            return mPostTime + duration + (extended ? mExtensionTime : 0);
         }
 
         /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt
index 04fe6b3..b37194b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt
@@ -59,6 +59,7 @@
      * Gets the touchable region needed for heads up notifications. Returns null if no touchable
      * region is required (ie: no heads up notification currently exists).
      */
+    // TODO(b/347007367): With scene container enabled this method may report outdated regions
     fun getTouchableRegion(): Region?
 
     /**
@@ -83,6 +84,10 @@
     /** Returns whether the entry is (pinned and expanded) or (has an active remote input). */
     fun isSticky(key: String?): Boolean
 
+    /**
+     * Returns the value of the tracking-heads-up flag. See the doc of {@code setTrackingHeadsUp} as
+     * well.
+     */
     fun isTrackingHeadsUp(): Boolean
 
     fun onExpandingFinished()
@@ -115,7 +120,7 @@
         key: String,
         releaseImmediately: Boolean,
         animate: Boolean,
-        reason: String
+        reason: String,
     ): Boolean
 
     /** Clears all managed notifications. */
@@ -149,6 +154,10 @@
      */
     fun setRemoteInputActive(entry: NotificationEntry, remoteInputActive: Boolean)
 
+    /**
+     * Sets the tracking-heads-up flag. If the flag is true, HeadsUpManager doesn't remove the entry
+     * from the list even after a Heads Up Notification is gone.
+     */
     fun setTrackingHeadsUp(tracking: Boolean)
 
     /** Sets the current user. */
@@ -260,7 +269,7 @@
         key: String,
         releaseImmediately: Boolean,
         animate: Boolean,
-        reason: String
+        reason: String,
     ) = false
 
     override fun setAnimationStateHandler(handler: AnimationStateHandler) {}
diff --git a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
index d97cae2..d367455 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
@@ -50,6 +50,7 @@
 public class SystemUIToast implements ToastPlugin.Toast {
     static final String TAG = "SystemUIToast";
     final Context mContext;
+    final Context mDisplayContext;
     final CharSequence mText;
     final ToastPlugin.Toast mPluginToast;
 
@@ -68,17 +69,18 @@
     @Nullable private final Animator mInAnimator;
     @Nullable private final Animator mOutAnimator;
 
-    SystemUIToast(LayoutInflater layoutInflater, Context context, CharSequence text,
-            String packageName, int userId, int orientation) {
-        this(layoutInflater, context, text, null, packageName, userId,
+    SystemUIToast(LayoutInflater layoutInflater, Context applicationContext, Context displayContext,
+            CharSequence text, String packageName, int userId, int orientation) {
+        this(layoutInflater, applicationContext, displayContext, text, null, packageName, userId,
                 orientation);
     }
 
-    SystemUIToast(LayoutInflater layoutInflater, Context context, CharSequence text,
-            ToastPlugin.Toast pluginToast, String packageName, @UserIdInt int userId,
-            int orientation) {
+    SystemUIToast(LayoutInflater layoutInflater, Context applicationContext, Context displayContext,
+            CharSequence text, ToastPlugin.Toast pluginToast, String packageName,
+            @UserIdInt int userId, int orientation) {
         mLayoutInflater = layoutInflater;
-        mContext = context;
+        mContext = applicationContext;
+        mDisplayContext = displayContext;
         mText = text;
         mPluginToast = pluginToast;
         mPackageName = packageName;
@@ -221,9 +223,9 @@
             mPluginToast.onOrientationChange(orientation);
         }
 
-        mDefaultY = mContext.getResources().getDimensionPixelSize(R.dimen.toast_y_offset);
+        mDefaultY = mDisplayContext.getResources().getDimensionPixelSize(R.dimen.toast_y_offset);
         mDefaultGravity =
-                mContext.getResources().getInteger(R.integer.config_toastDefaultGravity);
+                mDisplayContext.getResources().getInteger(R.integer.config_toastDefaultGravity);
     }
 
     private Animator createInAnimator() {
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java b/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
index 9ae6674..388d4bd 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastFactory.java
@@ -65,15 +65,16 @@
     /**
      * Create a toast to be shown by ToastUI.
      */
-    public SystemUIToast createToast(Context context, CharSequence text, String packageName,
-            int userId, int orientation) {
-        LayoutInflater layoutInflater = LayoutInflater.from(context);
+    public SystemUIToast createToast(Context applicationContext, Context displayContext,
+            CharSequence text, String packageName, int userId, int orientation) {
+        LayoutInflater layoutInflater = LayoutInflater.from(displayContext);
         if (isPluginAvailable()) {
-            return new SystemUIToast(layoutInflater, context, text, mPlugin.createToast(text,
-                    packageName, userId), packageName, userId, orientation);
+            return new SystemUIToast(layoutInflater, applicationContext, displayContext, text,
+                    mPlugin.createToast(text, packageName, userId), packageName, userId,
+                    orientation);
         }
-        return new SystemUIToast(layoutInflater, context, text, packageName, userId,
-                orientation);
+        return new SystemUIToast(layoutInflater, applicationContext, displayContext, text,
+                packageName, userId, orientation);
     }
 
     private boolean isPluginAvailable() {
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
index 32a4f12..12f73b8 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
@@ -135,8 +135,8 @@
                 return;
             }
             Context displayContext = context.createDisplayContext(display);
-            mToast = mToastFactory.createToast(displayContext /* sysuiContext */, text, packageName,
-                    userHandle.getIdentifier(), mOrientation);
+            mToast = mToastFactory.createToast(mContext, displayContext /* sysuiContext */, text,
+                    packageName, userHandle.getIdentifier(), mOrientation);
 
             if (mToast.getInAnimation() != null) {
                 mToast.getInAnimation().start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index e1845a1..7e85dd5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -213,7 +213,7 @@
             )
         val keyguardTouchHandlingInteractor =
             KeyguardTouchHandlingInteractor(
-                appContext = mContext,
+                context = mContext,
                 scope = testScope.backgroundScope,
                 transitionInteractor = kosmos.keyguardTransitionInteractor,
                 repository = repository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
index 0b9c06f..5ada2f3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
@@ -222,7 +222,7 @@
         when(SubscriptionManager.getDefaultDataSubscriptionId()).thenReturn(SUB_ID);
         SubscriptionInfo info = mock(SubscriptionInfo.class);
         when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(info);
-        when(mToastFactory.createToast(any(), anyString(), anyString(), anyInt(), anyInt()))
+        when(mToastFactory.createToast(any(), any(), anyString(), anyString(), anyInt(), anyInt()))
             .thenReturn(mSystemUIToast);
         when(mSystemUIToast.getView()).thenReturn(mToastView);
         when(mSystemUIToast.getGravity()).thenReturn(GRAVITY_FLAGS);
@@ -275,8 +275,8 @@
         mInternetDialogController.connectCarrierNetwork();
 
         verify(mMergedCarrierEntry).connect(null /* callback */, false /* showToast */);
-        verify(mToastFactory).createToast(any(), eq(TOAST_MESSAGE_STRING), anyString(), anyInt(),
-            anyInt());
+        verify(mToastFactory).createToast(any(), any(), eq(TOAST_MESSAGE_STRING), anyString(),
+                anyInt(), anyInt());
     }
 
     @Test
@@ -288,7 +288,7 @@
         mInternetDialogController.connectCarrierNetwork();
 
         verify(mMergedCarrierEntry, never()).connect(null /* callback */, false /* showToast */);
-        verify(mToastFactory, never()).createToast(any(), anyString(), anyString(), anyInt(),
+        verify(mToastFactory, never()).createToast(any(), any(), anyString(), anyString(), anyInt(),
             anyInt());
     }
 
@@ -302,7 +302,7 @@
         mInternetDialogController.connectCarrierNetwork();
 
         verify(mMergedCarrierEntry, never()).connect(null /* callback */, false /* showToast */);
-        verify(mToastFactory, never()).createToast(any(), anyString(), anyString(), anyInt(),
+        verify(mToastFactory, never()).createToast(any(), any(), anyString(), anyString(), anyInt(),
             anyInt());
     }
 
@@ -321,7 +321,7 @@
         mInternetDialogController.connectCarrierNetwork();
 
         verify(mMergedCarrierEntry, never()).connect(null /* callback */, false /* showToast */);
-        verify(mToastFactory, never()).createToast(any(), anyString(), anyString(), anyInt(),
+        verify(mToastFactory, never()).createToast(any(), any(), anyString(), anyString(), anyInt(),
             anyInt());
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index b142fc2..c9ada7e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -155,7 +155,6 @@
 import com.android.systemui.shade.ShadeControllerImpl;
 import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.shade.ShadeLogger;
-import com.android.systemui.shade.StatusBarLongPressGestureDetector;
 import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.KeyboardShortcutListSearch;
@@ -175,6 +174,7 @@
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.core.StatusBarConnectedDisplays;
 import com.android.systemui.statusbar.core.StatusBarInitializerImpl;
+import com.android.systemui.statusbar.core.StatusBarOrchestrator;
 import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository;
 import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
@@ -372,7 +372,7 @@
     @Mock private EmergencyGestureIntentFactory mEmergencyGestureIntentFactory;
     @Mock private NotificationSettingsInteractor mNotificationSettingsInteractor;
     @Mock private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
-    @Mock private StatusBarLongPressGestureDetector mStatusBarLongPressGestureDetector;
+    @Mock private StatusBarOrchestrator mStatusBarOrchestrator;
     private ShadeController mShadeController;
     private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
     private final FakeGlobalSettings mFakeGlobalSettings = new FakeGlobalSettings();
@@ -607,7 +607,6 @@
                 mShadeController,
                 mWindowRootViewVisibilityInteractor,
                 mStatusBarKeyguardViewManager,
-                () -> mStatusBarLongPressGestureDetector,
                 mViewMediatorCallback,
                 mInitController,
                 new Handler(TestableLooper.get(this).getLooper()),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 69efa87..638f195 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -40,13 +40,14 @@
 import com.android.systemui.plugins.fakeDarkIconDispatcher
 import com.android.systemui.res.R
 import com.android.systemui.scene.ui.view.WindowRootView
+import com.android.systemui.shade.LongPressGestureDetector
 import com.android.systemui.shade.ShadeControllerImpl
 import com.android.systemui.shade.ShadeLogger
 import com.android.systemui.shade.ShadeViewController
-import com.android.systemui.shade.StatusBarLongPressGestureDetector
 import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.data.repository.fakeStatusBarContentInsetsProviderStore
+import com.android.systemui.statusbar.data.repository.statusBarContentInsetsProviderStore
 import com.android.systemui.statusbar.policy.Clock
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.window.StatusBarWindowStateController
@@ -97,7 +98,7 @@
     @Mock private lateinit var windowRootView: Provider<WindowRootView>
     @Mock private lateinit var shadeLogger: ShadeLogger
     @Mock private lateinit var viewUtil: ViewUtil
-    @Mock private lateinit var mStatusBarLongPressGestureDetector: StatusBarLongPressGestureDetector
+    @Mock private lateinit var longPressGestureDetector: LongPressGestureDetector
     private lateinit var statusBarWindowStateController: StatusBarWindowStateController
 
     private lateinit var view: PhoneStatusBarView
@@ -394,7 +395,7 @@
                 shadeControllerImpl,
                 shadeViewController,
                 panelExpansionInteractor,
-                { mStatusBarLongPressGestureDetector },
+                { longPressGestureDetector },
                 windowRootView,
                 shadeLogger,
                 viewUtil,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt
index 92dc897..7fd9276 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayWindowPropertiesRepository.kt
@@ -35,4 +35,9 @@
                 )
                 .also { properties.put(displayId, windowType, it) }
     }
+
+    /** Sets an instance, just for testing purposes. */
+    fun insert(instance: DisplayWindowProperties) {
+        properties.put(instance.displayId, instance.windowType, instance)
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelKosmos.kt
new file mode 100644
index 0000000..b24b3ad
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.dreams.ui.viewmodel
+
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+
+val Kosmos.dreamUserActionsViewModel by
+    Kosmos.Fixture {
+        DreamUserActionsViewModel(
+            deviceUnlockedInteractor = deviceUnlockedInteractor,
+            shadeInteractor = shadeInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
index 32469b6..ad38bbe 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
@@ -23,13 +23,13 @@
 
 class FakeFeatureFlagsClassic : FakeFeatureFlags()
 
+val FeatureFlagsClassic.fake
+    get() = this as FakeFeatureFlagsClassic
+
 @Deprecated(
     message = "Use FakeFeatureFlagsClassic instead.",
     replaceWith =
-        ReplaceWith(
-            "FakeFeatureFlagsClassic",
-            "com.android.systemui.flags.FakeFeatureFlagsClassic",
-        ),
+        ReplaceWith("FakeFeatureFlagsClassic", "com.android.systemui.flags.FakeFeatureFlagsClassic"),
 )
 open class FakeFeatureFlags : FeatureFlagsClassic {
     private val booleanFlags = mutableMapOf<String, Boolean>()
@@ -105,6 +105,7 @@
                 listener.onFlagChanged(
                     object : FlagListenable.FlagEvent {
                         override val flagName = flag.name
+
                         override fun requestNoRestart() {}
                     }
                 )
@@ -165,7 +166,7 @@
 
 @Module(includes = [FakeFeatureFlagsClassicModule.Bindings::class])
 class FakeFeatureFlagsClassicModule(
-    @get:Provides val fakeFeatureFlagsClassic: FakeFeatureFlagsClassic = FakeFeatureFlagsClassic(),
+    @get:Provides val fakeFeatureFlagsClassic: FakeFeatureFlagsClassic = FakeFeatureFlagsClassic()
 ) {
 
     constructor(
@@ -175,7 +176,9 @@
     @Module
     interface Bindings {
         @Binds fun bindFake(fake: FakeFeatureFlagsClassic): FeatureFlagsClassic
+
         @Binds fun bindClassic(classic: FeatureFlagsClassic): FeatureFlags
+
         @Binds fun bindFakeClassic(fake: FakeFeatureFlagsClassic): FakeFeatureFlags
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
index f52f039..903bc8e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
@@ -109,6 +109,7 @@
             applicationCoroutineScope,
             testDispatcher,
             shortcutCategoriesUtils,
+            applicationContext,
         )
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt
index 769612c..255a780 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt
@@ -30,7 +30,7 @@
 val Kosmos.keyguardTouchHandlingInteractor by
     Kosmos.Fixture {
         KeyguardTouchHandlingInteractor(
-            appContext = applicationContext,
+            context = applicationContext,
             scope = applicationCoroutineScope,
             transitionInteractor = keyguardTransitionInteractor,
             repository = keyguardRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 63e6eb6..d1303d2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -71,14 +71,17 @@
 import com.android.systemui.shade.ui.viewmodel.notificationShadeWindowModel
 import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel
 import com.android.systemui.statusbar.data.repository.fakeStatusBarModePerDisplayRepository
+import com.android.systemui.statusbar.notification.collection.provider.visualStabilityProvider
 import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
+import com.android.systemui.statusbar.phone.keyguardBypassController
 import com.android.systemui.statusbar.phone.scrimController
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.fakeWifiRepository
 import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.wifiInteractor
+import com.android.systemui.statusbar.policy.configurationController
 import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
 import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
 import com.android.systemui.statusbar.ui.viewmodel.keyguardStatusBarViewModel
@@ -105,6 +108,7 @@
     val testScope by lazy { kosmos.testScope }
     val fakeExecutor by lazy { kosmos.fakeExecutor }
     val fakeExecutorHandler by lazy { kosmos.fakeExecutorHandler }
+    val configurationController by lazy { kosmos.configurationController }
     val configurationRepository by lazy { kosmos.fakeConfigurationRepository }
     val configurationInteractor by lazy { kosmos.configurationInteractor }
     val bouncerRepository by lazy { kosmos.bouncerRepository }
@@ -115,13 +119,14 @@
     val seenNotificationsInteractor by lazy { kosmos.seenNotificationsInteractor }
     val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
     val keyguardBouncerRepository by lazy { kosmos.fakeKeyguardBouncerRepository }
+    val keyguardBypassController by lazy { kosmos.keyguardBypassController }
     val keyguardInteractor by lazy { kosmos.keyguardInteractor }
     val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
     val keyguardTransitionInteractor by lazy { kosmos.keyguardTransitionInteractor }
     val keyguardStatusBarViewModel by lazy { kosmos.keyguardStatusBarViewModel }
     val powerRepository by lazy { kosmos.fakePowerRepository }
     val clock by lazy { kosmos.systemClock }
-    val mobileConnectionsRepository by lazy { kosmos.fakeMobileConnectionsRepository }
+    val mobileConnectionsRepository by lazy { kosmos.mobileConnectionsRepository }
     val simBouncerInteractor by lazy { kosmos.simBouncerInteractor }
     val statusBarStateController by lazy { kosmos.statusBarStateController }
     val statusBarModePerDisplayRepository by lazy { kosmos.fakeStatusBarModePerDisplayRepository }
@@ -158,6 +163,7 @@
     val shadeRepository by lazy { kosmos.shadeRepository }
     val shadeInteractor by lazy { kosmos.shadeInteractor }
     val notificationShadeWindowModel by lazy { kosmos.notificationShadeWindowModel }
+    val visualStabilityProvider by lazy { kosmos.visualStabilityProvider }
     val wifiInteractor by lazy { kosmos.wifiInteractor }
     val fakeWifiRepository by lazy { kosmos.fakeWifiRepository }
     val volumeDialogInteractor by lazy { kosmos.volumeDialogInteractor }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorKosmos.kt
index d76defe..99ed4f0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/airplane/domain/interactor/AirplaneModeInteractorKosmos.kt
@@ -18,7 +18,7 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
 
 val Kosmos.airplaneModeInteractor: AirplaneModeInteractor by
@@ -26,6 +26,6 @@
         AirplaneModeInteractor(
             FakeAirplaneModeRepository(),
             FakeConnectivityRepository(),
-            fakeMobileConnectionsRepository,
+            mobileConnectionsRepository,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
index de73d33..bfd46b6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
@@ -77,11 +77,7 @@
 
     override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
         return subIdRepos[subId]
-            ?: FakeMobileConnectionRepository(
-                    subId,
-                    tableLogBuffer,
-                )
-                .also { subIdRepos[subId] = it }
+            ?: FakeMobileConnectionRepository(subId, tableLogBuffer).also { subIdRepos[subId] = it }
     }
 
     override val defaultDataSubRatConfig = MutableStateFlow(MobileMappings.Config())
@@ -135,3 +131,6 @@
         const val LTE_ADVANCED_PRO = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO
     }
 }
+
+val MobileConnectionsRepository.fake
+    get() = this as FakeMobileConnectionsRepository
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryKosmos.kt
index cd22f1d..b952d71 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryKosmos.kt
@@ -19,12 +19,20 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.log.table.logcatTableLogBuffer
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
+
+val Kosmos.mobileMappingsProxy: MobileMappingsProxy by Fixture { FakeMobileMappingsProxy() }
+
+var Kosmos.mobileConnectionsRepositoryLogbufferName by Fixture { "FakeMobileConnectionsRepository" }
 
 val Kosmos.fakeMobileConnectionsRepository by Fixture {
     FakeMobileConnectionsRepository(
-        tableLogBuffer = logcatTableLogBuffer(this, "FakeMobileConnectionsRepository"),
+        mobileMappings = mobileMappingsProxy,
+        tableLogBuffer = logcatTableLogBuffer(this, mobileConnectionsRepositoryLogbufferName),
     )
 }
 
-val Kosmos.mobileConnectionsRepository by
-    Fixture<MobileConnectionsRepository> { fakeMobileConnectionsRepository }
+val Kosmos.mobileConnectionsRepository: MobileConnectionsRepository by Fixture {
+    fakeMobileConnectionsRepository
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryKosmos.kt
index 8e656cf..00bfa99 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryKosmos.kt
@@ -18,7 +18,5 @@
 
 import com.android.systemui.kosmos.Kosmos
 
-val Kosmos.fakeConnectivityRepository: FakeConnectivityRepository by
-    Kosmos.Fixture { FakeConnectivityRepository() }
 val Kosmos.connectivityRepository: ConnectivityRepository by
-    Kosmos.Fixture { fakeConnectivityRepository }
+    Kosmos.Fixture { FakeConnectivityRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
index 331e2fa..c69d9a2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt
@@ -42,10 +42,7 @@
      * validated
      */
     @JvmOverloads
-    fun setMobileConnected(
-        default: Boolean = true,
-        validated: Boolean = true,
-    ) {
+    fun setMobileConnected(default: Boolean = true, validated: Boolean = true) {
         defaultConnections.value =
             DefaultConnectionModel(
                 mobile = DefaultConnectionModel.Mobile(default),
@@ -55,10 +52,7 @@
 
     /** Similar convenience method for ethernet */
     @JvmOverloads
-    fun setEthernetConnected(
-        default: Boolean = true,
-        validated: Boolean = true,
-    ) {
+    fun setEthernetConnected(default: Boolean = true, validated: Boolean = true) {
         defaultConnections.value =
             DefaultConnectionModel(
                 ethernet = DefaultConnectionModel.Ethernet(default),
@@ -67,10 +61,7 @@
     }
 
     @JvmOverloads
-    fun setWifiConnected(
-        default: Boolean = true,
-        validated: Boolean = true,
-    ) {
+    fun setWifiConnected(default: Boolean = true, validated: Boolean = true) {
         defaultConnections.value =
             DefaultConnectionModel(
                 wifi = DefaultConnectionModel.Wifi(default),
@@ -78,3 +69,6 @@
             )
     }
 }
+
+val ConnectivityRepository.fake
+    get() = this as FakeConnectivityRepository
diff --git a/ravenwood/scripts/run-ravenwood-tests.sh b/ravenwood/scripts/run-ravenwood-tests.sh
index fe2269a..27c5ea1 100755
--- a/ravenwood/scripts/run-ravenwood-tests.sh
+++ b/ravenwood/scripts/run-ravenwood-tests.sh
@@ -33,7 +33,7 @@
 exclude_re=""
 smoke_exclude_re=""
 dry_run=""
-while getopts "sx:f:dt" opt; do
+while getopts "sx:f:dtb" opt; do
 case "$opt" in
     s)
         # Remove slow tests.
@@ -52,8 +52,13 @@
         dry_run="echo"
         ;;
     t)
+        # Redirect log to terminal
         export RAVENWOOD_LOG_OUT=$(tty)
         ;;
+    b)
+        # Build only
+        ATEST=m
+        ;;
     '?')
         exit 1
         ;;
@@ -99,11 +104,16 @@
 
 # Calculate the removed tests.
 
-diff="$(diff  <(echo "${all_tests[@]}" | tr ' ' '\n') <(echo "${targets[@]}" | tr ' ' '\n') )"
+diff="$(diff  <(echo "${all_tests[@]}" | tr ' ' '\n') <(echo "${targets[@]}" | tr ' ' '\n') | grep -v [0-9] )"
 
 if [[ "$diff" != "" ]]; then
     echo "Excluded tests:"
     echo "$diff"
 fi
 
-$dry_run ${ATEST:-atest} "${targets[@]}"
+run() {
+    echo "Running: ${@}"
+    "${@}"
+}
+
+run $dry_run ${ATEST:-atest} "${targets[@]}"
diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java
index cd2a535..e59bb42 100644
--- a/services/autofill/java/com/android/server/autofill/Helper.java
+++ b/services/autofill/java/com/android/server/autofill/Helper.java
@@ -28,8 +28,11 @@
 import android.app.assist.AssistStructure;
 import android.app.assist.AssistStructure.ViewNode;
 import android.app.assist.AssistStructure.WindowNode;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
 import android.content.ComponentName;
 import android.content.Context;
+import android.graphics.drawable.Icon;
 import android.hardware.display.DisplayManager;
 import android.metrics.LogMaker;
 import android.os.UserHandle;
@@ -97,11 +100,12 @@
             @UserIdInt int userId, @NonNull RemoteViews rView) {
         final AtomicBoolean permissionsOk = new AtomicBoolean(true);
 
-        rView.visitUris(uri -> {
-            int uriOwnerId = android.content.ContentProvider.getUserIdFromUri(uri);
-            boolean allowed = uriOwnerId == userId;
-            permissionsOk.set(allowed & permissionsOk.get());
-        });
+        rView.visitUris(
+                uri -> {
+                    int uriOwnerId = android.content.ContentProvider.getUserIdFromUri(uri, userId);
+                    boolean allowed = uriOwnerId == userId;
+                    permissionsOk.set(allowed & permissionsOk.get());
+                });
 
         return permissionsOk.get();
     }
@@ -150,6 +154,47 @@
         return (ok ? rView : null);
     }
 
+    /**
+     * Checks the URI permissions of the icon in the slice, to see if the current userId is able to
+     * access it.
+     *
+     * <p>Returns null if slice contains user inaccessible icons
+     *
+     * <p>TODO: instead of returning a null Slice when the current userId cannot access an icon,
+     * return a reconstructed Slice without the icons. This is currently non-trivial since there are
+     * no public methods to generically add SliceItems to Slices
+     */
+    public static @Nullable Slice sanitizeSlice(Slice slice) {
+        if (slice == null) {
+            return null;
+        }
+
+        int userId = ActivityManager.getCurrentUser();
+
+        // Recontruct the Slice, filtering out bad icons
+        for (SliceItem sliceItem : slice.getItems()) {
+            if (!sliceItem.getFormat().equals(SliceItem.FORMAT_IMAGE)) {
+                // Not an image slice
+                continue;
+            }
+
+            Icon icon = sliceItem.getIcon();
+            if (icon.getType() != Icon.TYPE_URI
+                    && icon.getType() != Icon.TYPE_URI_ADAPTIVE_BITMAP) {
+                // No URIs to sanitize
+                continue;
+            }
+
+            int iconUriId = android.content.ContentProvider.getUserIdFromUri(icon.getUri(), userId);
+
+            if (iconUriId != userId) {
+                Slog.w(TAG, "sanitizeSlice() user: " + userId + " cannot access icons in Slice");
+                return null;
+            }
+        }
+
+        return slice;
+    }
 
     @Nullable
     static AutofillId[] toArray(@Nullable ArraySet<AutofillId> set) {
diff --git a/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java b/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java
index 38a412f..50a26b3 100644
--- a/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java
+++ b/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java
@@ -27,6 +27,7 @@
 import android.util.Slog;
 
 import com.android.server.LocalServices;
+import com.android.server.autofill.Helper;
 import com.android.server.autofill.RemoteInlineSuggestionRenderService;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 
@@ -83,6 +84,10 @@
      */
     public boolean renderSuggestion(int width, int height,
             @NonNull IInlineSuggestionUiCallback callback) {
+        if (Helper.sanitizeSlice(mInlinePresentation.getSlice()) == null) {
+            if (sDebug) Slog.d(TAG, "Skipped rendering inline suggestion.");
+            return false;
+        }
         if (mRemoteRenderService != null) {
             if (sDebug) Slog.d(TAG, "Request to recreate the UI");
             mRemoteRenderService.renderSuggestion(callback, mInlinePresentation, width, height,
diff --git a/services/core/java/com/android/server/am/BroadcastFilter.java b/services/core/java/com/android/server/am/BroadcastFilter.java
index 3c7fb52..d899228 100644
--- a/services/core/java/com/android/server/am/BroadcastFilter.java
+++ b/services/core/java/com/android/server/am/BroadcastFilter.java
@@ -40,7 +40,7 @@
     @ChangeId
     @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.BASE)
     @VisibleForTesting
-    static final long CHANGE_RESTRICT_PRIORITY_VALUES = 371309185L;
+    static final long RESTRICT_PRIORITY_VALUES = 371309185L;
 
     // Back-pointer to the list this filter is in.
     final ReceiverList receiverList;
@@ -130,7 +130,7 @@
             return priority;
         }
         if (!platformCompat.isChangeEnabledByUidInternalNoLogging(
-                CHANGE_RESTRICT_PRIORITY_VALUES, owningUid)) {
+                RESTRICT_PRIORITY_VALUES, owningUid)) {
             return priority;
         }
         if (!UserHandle.isCore(owningUid)) {
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 38df10a..e8ce173 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -88,7 +88,7 @@
     @ChangeId
     @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.BASE)
     @VisibleForTesting
-    static final long CHANGE_LIMIT_PRIORITY_SCOPE = 371307720L;
+    static final long LIMIT_PRIORITY_SCOPE = 371307720L;
 
     final @NonNull Intent intent;    // the original intent that generated us
     final @Nullable ComponentName targetComp; // original component name set on the intent
@@ -781,7 +781,7 @@
         } else {
             if (Flags.limitPriorityScope()) {
                 final boolean[] changeEnabled = calculateChangeStateForReceivers(
-                        receivers, CHANGE_LIMIT_PRIORITY_SCOPE, platformCompat);
+                        receivers, LIMIT_PRIORITY_SCOPE, platformCompat);
 
                 // Priority of the previous tranche
                 int lastTranchePriority = 0;
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 5a2610b..abb756b 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -2460,6 +2460,15 @@
                 DisplayManagerGlobal.EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED);
     }
 
+    private void handleLogicalDisplayRefreshRateChangedLocked(@NonNull LogicalDisplay display) {
+        sendDisplayEventIfEnabledLocked(display,
+                DisplayManagerGlobal.EVENT_DISPLAY_REFRESH_RATE_CHANGED);
+    }
+
+    private void handleLogicalDisplayStateChangedLocked(@NonNull LogicalDisplay display) {
+        sendDisplayEventIfEnabledLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_STATE_CHANGED);
+    }
+
     private void notifyDefaultDisplayDeviceUpdated(LogicalDisplay display) {
         mDisplayModeDirector.defaultDisplayDeviceUpdated(display.getPrimaryDisplayDeviceLocked()
                 .mDisplayDeviceConfig);
@@ -3991,6 +4000,12 @@
                 case LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_DISCONNECTED:
                     handleLogicalDisplayDisconnectedLocked(display);
                     break;
+                case LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED:
+                    handleLogicalDisplayRefreshRateChangedLocked(display);
+                    break;
+                case LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_STATE_CHANGED:
+                    handleLogicalDisplayStateChangedLocked(display);
+                    break;
             }
         }
 
@@ -4198,6 +4213,13 @@
                     return (mask
                             & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED)
                             != 0;
+                case DisplayManagerGlobal.EVENT_DISPLAY_REFRESH_RATE_CHANGED:
+                    return (mask
+                            & DisplayManagerGlobal
+                            .INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE) != 0;
+                case DisplayManagerGlobal.EVENT_DISPLAY_STATE_CHANGED:
+                    return (mask & DisplayManagerGlobal
+                            .INTERNAL_EVENT_FLAG_DISPLAY_STATE) != 0;
                 default:
                     // This should never happen.
                     Slog.e(TAG, "Unknown display event " + event);
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 09fa4e6..c0903a9 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -79,15 +79,18 @@
     // 'adb shell setprop persist.log.tag.LogicalDisplayMapper DEBUG && adb reboot'
     private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
 
-    public static final int LOGICAL_DISPLAY_EVENT_ADDED = 1;
-    public static final int LOGICAL_DISPLAY_EVENT_CHANGED = 2;
-    public static final int LOGICAL_DISPLAY_EVENT_REMOVED = 3;
-    public static final int LOGICAL_DISPLAY_EVENT_SWAPPED = 4;
-    public static final int LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED = 5;
-    public static final int LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION = 6;
-    public static final int LOGICAL_DISPLAY_EVENT_HDR_SDR_RATIO_CHANGED = 7;
-    public static final int LOGICAL_DISPLAY_EVENT_CONNECTED = 8;
-    public static final int LOGICAL_DISPLAY_EVENT_DISCONNECTED = 9;
+    public static final int LOGICAL_DISPLAY_EVENT_BASE = 0;
+    public static final int LOGICAL_DISPLAY_EVENT_ADDED = 1 << 0;
+    public static final int LOGICAL_DISPLAY_EVENT_CHANGED = 1 << 1;
+    public static final int LOGICAL_DISPLAY_EVENT_REMOVED = 1 << 2;
+    public static final int LOGICAL_DISPLAY_EVENT_SWAPPED = 1 << 3;
+    public static final int LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED = 1 << 4;
+    public static final int LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION = 1 << 5;
+    public static final int LOGICAL_DISPLAY_EVENT_HDR_SDR_RATIO_CHANGED = 1 << 6;
+    public static final int LOGICAL_DISPLAY_EVENT_CONNECTED = 1 << 7;
+    public static final int LOGICAL_DISPLAY_EVENT_DISCONNECTED = 1 << 8;
+    public static final int LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED = 1 << 9;
+    public static final int LOGICAL_DISPLAY_EVENT_STATE_CHANGED = 1 << 10;
 
     public static final int DISPLAY_GROUP_EVENT_ADDED = 1;
     public static final int DISPLAY_GROUP_EVENT_CHANGED = 2;
@@ -804,6 +807,8 @@
             final boolean wasPreviouslyUpdated = updateState != UPDATE_STATE_NEW;
             final boolean wasPreviouslyEnabled = mDisplaysEnabledCache.get(displayId);
             final boolean isCurrentlyEnabled = display.isEnabledLocked();
+            int logicalDisplayEventMask = mLogicalDisplaysToUpdate
+                    .get(displayId, LOGICAL_DISPLAY_EVENT_BASE);
 
             // The display is no longer valid and needs to be removed.
             if (!display.isValidLocked()) {
@@ -821,20 +826,20 @@
                         if (mDisplaysEnabledCache.get(displayId)) {
                             // We still need to send LOGICAL_DISPLAY_EVENT_DISCONNECTED
                             reloop = true;
-                            mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_REMOVED);
+                            logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_REMOVED;
                         } else {
                             mUpdatedLogicalDisplays.delete(displayId);
-                            mLogicalDisplaysToUpdate.put(displayId,
-                                    LOGICAL_DISPLAY_EVENT_DISCONNECTED);
+                            logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_DISCONNECTED;
                         }
                     } else {
                         mUpdatedLogicalDisplays.delete(displayId);
-                        mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_REMOVED);
+                        logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_REMOVED;
                     }
                 } else {
                     // This display never left this class, safe to remove without notification
                     mLogicalDisplays.removeAt(i);
                 }
+                mLogicalDisplaysToUpdate.put(displayId, logicalDisplayEventMask);
                 continue;
 
             // The display is new.
@@ -842,38 +847,40 @@
                 if (mFlags.isConnectedDisplayManagementEnabled()) {
                     // We still need to send LOGICAL_DISPLAY_EVENT_ADDED
                     reloop = true;
-                    mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_CONNECTED);
+                    logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_CONNECTED;
                 } else {
-                    mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_ADDED);
+                    logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_ADDED;
                 }
             // Underlying displays device has changed to a different one.
             } else if (!TextUtils.equals(mTempDisplayInfo.uniqueId, newDisplayInfo.uniqueId)) {
-                mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_SWAPPED);
+                logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_SWAPPED;
 
             // Something about the display device has changed.
             } else if (mFlags.isConnectedDisplayManagementEnabled()
                     && wasPreviouslyEnabled != isCurrentlyEnabled) {
                 int event = isCurrentlyEnabled ? LOGICAL_DISPLAY_EVENT_ADDED :
                         LOGICAL_DISPLAY_EVENT_REMOVED;
-                mLogicalDisplaysToUpdate.put(displayId, event);
+                logicalDisplayEventMask |= event;
             } else if (wasDirty || !mTempDisplayInfo.equals(newDisplayInfo)) {
                 // If only the hdr/sdr ratio changed, then send just the event for that case
                 if ((diff == DisplayDeviceInfo.DIFF_HDR_SDR_RATIO)) {
-                    mLogicalDisplaysToUpdate.put(displayId,
-                            LOGICAL_DISPLAY_EVENT_HDR_SDR_RATIO_CHANGED);
+                    logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_HDR_SDR_RATIO_CHANGED;
                 } else {
-                    mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_CHANGED);
+                    logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_CHANGED;
                 }
 
-            // The display is involved in a display layout transition
+                if (mFlags.isDisplayListenerPerformanceImprovementsEnabled()) {
+                    logicalDisplayEventMask
+                            |= updateAndGetMaskForDisplayPropertyChanges(newDisplayInfo);
+                }
+
+                // The display is involved in a display layout transition
             } else if (updateState == UPDATE_STATE_TRANSITION) {
-                mLogicalDisplaysToUpdate.put(displayId,
-                        LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION);
+                logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION;
 
             // Display frame rate overrides changed.
             } else if (!display.getPendingFrameRateOverrideUids().isEmpty()) {
-                mLogicalDisplaysToUpdate.put(
-                        displayId, LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED);
+                logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED;
 
             // Non-override display values changed.
             } else {
@@ -882,10 +889,10 @@
                 // things like display cutouts.
                 display.getNonOverrideDisplayInfoLocked(mTempDisplayInfo);
                 if (!mTempNonOverrideDisplayInfo.equals(mTempDisplayInfo)) {
-                    mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_CHANGED);
+                    logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_CHANGED;
                 }
             }
-
+            mLogicalDisplaysToUpdate.put(displayId, logicalDisplayEventMask);
             mUpdatedLogicalDisplays.put(displayId, UPDATE_STATE_UPDATED);
         }
 
@@ -922,6 +929,8 @@
             sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_DISCONNECTED);
         }
         sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_CHANGED);
+        sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED);
+        sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_STATE_CHANGED);
         sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED);
         sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_SWAPPED);
         if (mFlags.isConnectedDisplayManagementEnabled()) {
@@ -944,13 +953,25 @@
         }
     }
 
+    @VisibleForTesting
+    int updateAndGetMaskForDisplayPropertyChanges(DisplayInfo newDisplayInfo) {
+        int mask = LOGICAL_DISPLAY_EVENT_BASE;
+        if (mTempDisplayInfo.getRefreshRate() != newDisplayInfo.getRefreshRate()) {
+            mask |= LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED;
+        }
+
+        if (mTempDisplayInfo.state != newDisplayInfo.state) {
+            mask |= LOGICAL_DISPLAY_EVENT_STATE_CHANGED;
+        }
+        return mask;
+    }
     /**
      * Send the specified message for all relevant displays in the specified display-to-message map.
      */
-    private void sendUpdatesForDisplaysLocked(int msg) {
+    private void sendUpdatesForDisplaysLocked(int logicalDisplayEvent) {
         for (int i = mLogicalDisplaysToUpdate.size() - 1; i >= 0; --i) {
-            final int currMsg = mLogicalDisplaysToUpdate.valueAt(i);
-            if (currMsg != msg) {
+            final int logicalDisplayEventMask = mLogicalDisplaysToUpdate.valueAt(i);
+            if ((logicalDisplayEventMask & logicalDisplayEvent) == 0) {
                 continue;
             }
 
@@ -959,25 +980,25 @@
             if (DEBUG) {
                 final DisplayDevice device = display.getPrimaryDisplayDeviceLocked();
                 final String uniqueId = device == null ? "null" : device.getUniqueId();
-                Slog.d(TAG, "Sending " + displayEventToString(msg) + " for display=" + id
-                        + " with device=" + uniqueId);
+                Slog.d(TAG, "Sending " + displayEventToString(logicalDisplayEvent) + " for "
+                        + "display=" + id + " with device=" + uniqueId);
             }
 
             if (mFlags.isConnectedDisplayManagementEnabled()) {
-                if (msg == LOGICAL_DISPLAY_EVENT_ADDED) {
+                if (logicalDisplayEvent == LOGICAL_DISPLAY_EVENT_ADDED) {
                     mDisplaysEnabledCache.put(id, true);
-                } else if (msg == LOGICAL_DISPLAY_EVENT_REMOVED) {
+                } else if (logicalDisplayEvent == LOGICAL_DISPLAY_EVENT_REMOVED) {
                     mDisplaysEnabledCache.delete(id);
                 }
             }
 
-            mListener.onLogicalDisplayEventLocked(display, msg);
+            mListener.onLogicalDisplayEventLocked(display, logicalDisplayEvent);
 
             if (mFlags.isConnectedDisplayManagementEnabled()) {
-                if (msg == LOGICAL_DISPLAY_EVENT_DISCONNECTED) {
+                if (logicalDisplayEvent == LOGICAL_DISPLAY_EVENT_DISCONNECTED) {
                     mLogicalDisplays.delete(id);
                 }
-            } else if (msg == LOGICAL_DISPLAY_EVENT_REMOVED) {
+            } else if (logicalDisplayEvent == LOGICAL_DISPLAY_EVENT_REMOVED) {
                 // We wait until we sent the EVENT_REMOVED event before actually removing the
                 // display.
                 mLogicalDisplays.delete(id);
@@ -1348,6 +1369,10 @@
                 return "connected";
             case LOGICAL_DISPLAY_EVENT_DISCONNECTED:
                 return "disconnected";
+            case LOGICAL_DISPLAY_EVENT_STATE_CHANGED:
+                return "state_changed";
+            case LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED:
+                return "refresh_rate_changed";
         }
         return null;
     }
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index 71f17d1..1a7d74a 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -246,6 +246,10 @@
             Flags.FLAG_ENABLE_PLUGIN_MANAGER,
             Flags::enablePluginManager
     );
+    private final FlagState mDisplayListenerPerformanceImprovementsFlagState = new FlagState(
+            Flags.FLAG_DISPLAY_LISTENER_PERFORMANCE_IMPROVEMENTS,
+            Flags::displayListenerPerformanceImprovements
+    );
 
     /**
      * @return {@code true} if 'port' is allowed in display layout configuration file.
@@ -527,6 +531,13 @@
     }
 
     /**
+     * @return {@code true} if the flag for display listener performance improvements is enabled
+     */
+    public boolean isDisplayListenerPerformanceImprovementsEnabled() {
+        return mDisplayListenerPerformanceImprovementsFlagState.isEnabled();
+    }
+
+    /**
      * dumps all flagstates
      * @param pw printWriter
      */
@@ -578,6 +589,7 @@
         pw.println(" " + mHasArrSupport);
         pw.println(" " + mAutoBrightnessModeBedtimeWearFlagState);
         pw.println(" " + mEnablePluginManagerFlagState);
+        pw.println(" " + mDisplayListenerPerformanceImprovementsFlagState);
     }
 
     private static class FlagState {
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index b509544..586d594 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -440,6 +440,14 @@
 }
 
 flag {
+    name: "display_listener_performance_improvements"
+    namespace: "display_manager"
+    description: "Feature flag for an API to let the apps subscribe to a specific property change of the Display."
+    bug: "372700957"
+    is_fixed_read_only: true
+}
+
+flag {
     name: "enable_get_supported_refresh_rates"
     namespace: "core_graphics"
     description: "Flag to use the surfaceflinger rates for getSupportedRefreshRates"
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index f4dd717..82449ce 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -623,9 +623,10 @@
         mKeyRemapper.systemRunning();
         mPointerIconCache.systemRunning();
         mKeyboardGlyphManager.systemRunning();
-        mKeyGestureController.systemRunning();
-
-        initKeyGestures();
+        if (useKeyGestureEventHandler()) {
+            mKeyGestureController.systemRunning();
+            initKeyGestures();
+        }
     }
 
     private void reloadDeviceAliases() {
@@ -2608,9 +2609,6 @@
     }
 
     private void initKeyGestures() {
-        if (!useKeyGestureEventHandler()) {
-            return;
-        }
         InputManager im = Objects.requireNonNull(mContext.getSystemService(InputManager.class));
         im.registerKeyGestureEventHandler(new InputManager.KeyGestureEventHandler() {
             @Override
diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java
index 5914dbe..7fd9620 100644
--- a/services/core/java/com/android/server/notification/GroupHelper.java
+++ b/services/core/java/com/android/server/notification/GroupHelper.java
@@ -92,10 +92,16 @@
     static final int REGROUP_REASON_CHANNEL_UPDATE = 0;
     // Regrouping needed because of notification bundling
     static final int REGROUP_REASON_BUNDLE = 1;
+    // Regrouping needed because of notification unbundling
+    static final int REGROUP_REASON_UNBUNDLE = 2;
+    // Regrouping needed because of notification unbundling + the original group summary exists
+    static final int REGROUP_REASON_UNBUNDLE_ORIGINAL_GROUP = 3;
 
     @IntDef(prefix = { "REGROUP_REASON_" }, value = {
         REGROUP_REASON_CHANNEL_UPDATE,
         REGROUP_REASON_BUNDLE,
+        REGROUP_REASON_UNBUNDLE,
+        REGROUP_REASON_UNBUNDLE_ORIGINAL_GROUP,
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface RegroupingReason {}
@@ -103,7 +109,6 @@
     private final Callback mCallback;
     private final int mAutoGroupAtCount;
     private final int mAutogroupSparseGroupsAtCount;
-    private final int mAutoGroupRegroupingAtCount;
     private final Context mContext;
     private final PackageManager mPackageManager;
     private boolean mIsTestHarnessExempted;
@@ -190,11 +195,6 @@
         mContext = context;
         mPackageManager = packageManager;
         mAutogroupSparseGroupsAtCount = autoGroupSparseGroupsAtCount;
-        if (notificationRegroupOnClassification()) {
-            mAutoGroupRegroupingAtCount = 1;
-        } else {
-            mAutoGroupRegroupingAtCount = mAutoGroupAtCount;
-        }
         NOTIFICATION_SHADE_SECTIONS = getNotificationShadeSections();
     }
 
@@ -797,7 +797,8 @@
                         Slog.v(TAG, "isGroupChildInDifferentBundleThanSummary: " + record);
                     }
                     moveNotificationsToNewSection(record.getUserId(), pkgName,
-                            List.of(new NotificationMoveOp(record, null, fullAggregateGroupKey)));
+                            List.of(new NotificationMoveOp(record, null, fullAggregateGroupKey)),
+                            REGROUP_REASON_BUNDLE);
                     return;
                 }
             }
@@ -945,6 +946,27 @@
         }
     }
 
+    /**
+     * Called when a notification that was classified (bundled) is restored to its original channel.
+     * The notification will be restored to its original group, if any/if summary still exists.
+     * Otherwise it will be moved to the appropriate section as an ungrouped notification.
+     *
+     * @param record the notification which had its channel updated
+     * @param originalSummaryExists the original group summary exists
+     */
+    @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING)
+    public void onNotificationUnbundled(final NotificationRecord record,
+            final boolean originalSummaryExists) {
+        synchronized (mAggregatedNotifications) {
+            ArrayMap<String, NotificationRecord> notificationsToCheck = new ArrayMap<>();
+            notificationsToCheck.put(record.getKey(), record);
+            regroupNotifications(record.getUserId(), record.getSbn().getPackageName(),
+                    notificationsToCheck,
+                    originalSummaryExists ? REGROUP_REASON_UNBUNDLE_ORIGINAL_GROUP
+                        : REGROUP_REASON_UNBUNDLE);
+        }
+    }
+
     @GuardedBy("mAggregatedNotifications")
     private void regroupNotifications(int userId, String pkgName,
             ArrayMap<String, NotificationRecord> notificationsToCheck,
@@ -973,7 +995,7 @@
 
         // Batch move to new section
         if (!notificationsToMove.isEmpty()) {
-            moveNotificationsToNewSection(userId, pkgName, notificationsToMove);
+            moveNotificationsToNewSection(userId, pkgName, notificationsToMove, regroupingReason);
         }
     }
 
@@ -1093,7 +1115,7 @@
 
     @GuardedBy("mAggregatedNotifications")
     private void moveNotificationsToNewSection(final int userId, final String pkgName,
-            final List<NotificationMoveOp> notificationsToMove) {
+            final List<NotificationMoveOp> notificationsToMove, int regroupingReason) {
         record GroupUpdateOp(FullyQualifiedGroupKey groupKey, NotificationRecord record,
                              boolean hasSummary) { }
         // Bundled operations to apply to groups affected by the channel update
@@ -1111,7 +1133,8 @@
             if (DEBUG) {
                 Log.i(TAG,
                     "moveNotificationToNewSection: " + record + " " + newFullAggregateGroupKey
-                        + " from: " + oldFullAggregateGroupKey);
+                            + " from: " + oldFullAggregateGroupKey + " regroupingReason: "
+                            + regroupingReason);
             }
 
             // Update/remove aggregate summary for old group
@@ -1140,28 +1163,35 @@
             // Add moved notifications to the ungrouped list for new group and do grouping
             // after all notifications have been handled
             if (newFullAggregateGroupKey != null) {
-                final ArrayMap<String, NotificationAttributes> newAggregatedNotificationsAttrs =
+                if (notificationRegroupOnClassification()
+                        && regroupingReason == REGROUP_REASON_UNBUNDLE_ORIGINAL_GROUP) {
+                    // Just reset override group key, original summary exists
+                    // => will be grouped back to its original group
+                    record.setOverrideGroupKey(null);
+                } else {
+                    final ArrayMap<String, NotificationAttributes> newAggregatedNotificationsAttrs =
                         mAggregatedNotifications.getOrDefault(newFullAggregateGroupKey,
                             new ArrayMap<>());
-                boolean hasSummary = !newAggregatedNotificationsAttrs.isEmpty();
-                ArrayMap<String, NotificationAttributes> ungrouped =
+                    boolean hasSummary = !newAggregatedNotificationsAttrs.isEmpty();
+                    ArrayMap<String, NotificationAttributes> ungrouped =
                         mUngroupedAbuseNotifications.getOrDefault(newFullAggregateGroupKey,
                             new ArrayMap<>());
-                ungrouped.put(record.getKey(), new NotificationAttributes(
+                    ungrouped.put(record.getKey(), new NotificationAttributes(
                         record.getFlags(),
                         record.getNotification().getSmallIcon(),
                         record.getNotification().color,
                         record.getNotification().visibility,
                         record.getNotification().getGroupAlertBehavior(),
                         record.getChannel().getId()));
-                mUngroupedAbuseNotifications.put(newFullAggregateGroupKey, ungrouped);
+                    mUngroupedAbuseNotifications.put(newFullAggregateGroupKey, ungrouped);
 
-                record.setOverrideGroupKey(null);
+                    record.setOverrideGroupKey(null);
 
-                // Only add once, for triggering notification
-                if (!groupsToUpdate.containsKey(newFullAggregateGroupKey)) {
-                    groupsToUpdate.put(newFullAggregateGroupKey,
-                        new GroupUpdateOp(newFullAggregateGroupKey, record, hasSummary));
+                    // Only add once, for triggering notification
+                    if (!groupsToUpdate.containsKey(newFullAggregateGroupKey)) {
+                        groupsToUpdate.put(newFullAggregateGroupKey,
+                            new GroupUpdateOp(newFullAggregateGroupKey, record, hasSummary));
+                    }
                 }
             }
         }
@@ -1176,7 +1206,7 @@
             NotificationRecord triggeringNotification = groupsToUpdate.get(groupKey).record;
             boolean hasSummary = groupsToUpdate.get(groupKey).hasSummary;
             //Group needs to be created/updated
-            if (ungrouped.size() >= mAutoGroupRegroupingAtCount
+            if (ungrouped.size() >= mAutoGroupAtCount
                     || (hasSummary && !aggregatedNotificationsAttrs.isEmpty())) {
                 NotificationSectioner sectioner = getSection(triggeringNotification);
                 if (sectioner == null) {
@@ -1436,7 +1466,8 @@
         }
     }
 
-    private ArrayMap<String, NotificationRecord> getSparseGroups(
+    @VisibleForTesting
+    protected ArrayMap<String, NotificationRecord> getSparseGroups(
             final FullyQualifiedGroupKey fullAggregateGroupKey,
             final List<NotificationRecord> notificationList,
             final Map<String, NotificationRecord> summaryByGroupKey,
@@ -1448,8 +1479,8 @@
                         && summary.getUserId() == fullAggregateGroupKey.userId
                         && summary.getSbn().isAppGroup()
                         && !summary.getGroupKey().equals(fullAggregateGroupKey.toString())) {
-                    int numChildren = getNumChildrenForGroup(summary.getSbn().getGroup(),
-                            notificationList);
+                    int numChildren = getNumChildrenForGroupWithSection(summary.getSbn().getGroup(),
+                            notificationList, sectioner);
                     if (numChildren > 0 && numChildren < MIN_CHILD_COUNT_TO_AVOID_FORCE_GROUPING) {
                         sparseGroups.put(summary.getGroupKey(), summary);
                     }
@@ -1459,6 +1490,43 @@
         return sparseGroups;
     }
 
+    /**
+     *  Get the number of children of a group if all match a certain section.
+     *  Used for force grouping sparse groups, where the summary may match a section but the
+     *  child notifications do not: ie. conversations
+     *
+     * @param groupKey the group key (name)
+     * @param notificationList all notifications list
+     * @param sectioner the section to match
+     * @return number of children in that group or -1 if section does not match
+     */
+    private int getNumChildrenForGroupWithSection(final String groupKey,
+            final List<NotificationRecord> notificationList,
+            final NotificationSectioner sectioner) {
+        int numChildren = 0;
+        for (NotificationRecord r : notificationList) {
+            if (!r.getNotification().isGroupSummary() && groupKey.equals(r.getSbn().getGroup())) {
+                NotificationSectioner childSection = getSection(r);
+                if (childSection == null || childSection != sectioner) {
+                    if (DEBUG) {
+                        Slog.i(TAG,
+                                "getNumChildrenForGroupWithSection skip because invalid section: "
+                                    + groupKey + " r: " + r);
+                    }
+                    return -1;
+                } else {
+                    numChildren++;
+                }
+            }
+        }
+
+        if (DEBUG) {
+            Slog.i(TAG,
+                    "getNumChildrenForGroupWithSection " + groupKey + " numChild: " + numChildren);
+        }
+        return numChildren;
+    }
+
     @GuardedBy("mAggregatedNotifications")
     private void cacheCanceledSummary(NotificationRecord record) {
         final FullyQualifiedGroupKey groupKey = new FullyQualifiedGroupKey(record.getUserId(),
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 0d8880a..5182dfe 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -7180,13 +7180,16 @@
                         Slog.i(TAG, "Removing app summary (all children bundled): "
                                 + groupSummary);
                     }
-                    canceledSummary = groupSummary;
-                    mSummaryByGroupKey.remove(oldGroupKey);
-                    cancelNotification(Binder.getCallingUid(), Binder.getCallingPid(),
+                    if (convertSummaryToNotificationLocked(groupSummary.getKey())) {
+                        groupSummary.isCanceled = true;
+                        canceledSummary = groupSummary;
+                        mSummaryByGroupKey.remove(oldGroupKey);
+                        cancelNotification(Binder.getCallingUid(), Binder.getCallingPid(),
                             groupSummary.getSbn().getPackageName(),
                             groupSummary.getSbn().getTag(),
                             groupSummary.getSbn().getId(), 0, 0, false, groupSummary.getUserId(),
                             NotificationListenerService.REASON_GROUP_OPTIMIZATION, null);
+                    }
                 }
             }
         }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 06e29c2..b2b8aaf 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -4984,7 +4984,10 @@
             res.getValue(com.android.internal.R.string.owner_name, mOwnerNameTypedValue, true);
             final CharSequence ownerName = mOwnerNameTypedValue.coerceToString();
             mOwnerName.set(ownerName != null ? ownerName.toString() : null);
+            // Invalidate when owners name changes due to config change.
+            UserManager.invalidateCacheOnUserDataChanged();
         }
+
     }
 
     private void scheduleWriteUserList() {
@@ -4997,6 +5000,8 @@
             Message msg = mHandler.obtainMessage(WRITE_USER_LIST_MSG);
             mHandler.sendMessageDelayed(msg, WRITE_USER_DELAY);
         }
+        // Invalidate cache when {@link UserData} changed, but write was scheduled for later.
+        UserManager.invalidateCacheOnUserDataChanged();
     }
 
     private void scheduleWriteUser(@UserIdInt int userId) {
@@ -5009,6 +5014,8 @@
             Message msg = mHandler.obtainMessage(WRITE_USER_MSG, userId);
             mHandler.sendMessageDelayed(msg, WRITE_USER_DELAY);
         }
+        // Invalidate cache when {@link Data} changed, but write was scheduled for later.
+        UserManager.invalidateCacheOnUserDataChanged();
     }
 
     private ResilientAtomicFile getUserFile(int userId) {
@@ -5032,6 +5039,9 @@
         if (DBG) {
             debug("writeUserLP " + userData);
         }
+        // invalidate caches related to any {@link UserData} change.
+        UserManager.invalidateCacheOnUserDataChanged();
+
         try (ResilientAtomicFile userFile = getUserFile(userData.info.id)) {
             FileOutputStream fos = null;
             try {
@@ -5196,6 +5206,8 @@
         if (DBG) {
             debug("writeUserList");
         }
+        // invalidate caches related to any {@link UserData} change.
+        UserManager.invalidateCacheOnUserDataChanged();
 
         try (ResilientAtomicFile file = getUserListFile()) {
             FileOutputStream fos = null;
@@ -7958,7 +7970,7 @@
                                     Settings.Secure.getIntForUser(mContext.getContentResolver(),
                                             HIDE_PRIVATESPACE_ENTRY_POINT, parentId) == 1);
                         } catch (Settings.SettingNotFoundException e) {
-                            throw new RuntimeException(e);
+                            config.putBoolean(PRIVATE_SPACE_ENTRYPOINT_HIDDEN, false);
                         }
                     }
                     return new LauncherUserInfo.Builder(userDetails.getName(),
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index 2c0ce25..17459df 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -33,11 +33,15 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.hardware.power.ChannelConfig;
+import android.hardware.power.CpuHeadroomParams;
+import android.hardware.power.GpuHeadroomParams;
 import android.hardware.power.IPower;
 import android.hardware.power.SessionConfig;
 import android.hardware.power.SessionTag;
 import android.hardware.power.WorkDuration;
 import android.os.Binder;
+import android.os.CpuHeadroomParamsInternal;
+import android.os.GpuHeadroomParamsInternal;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.IHintManager;
@@ -90,6 +94,10 @@
 
     private static final int EVENT_CLEAN_UP_UID = 3;
     @VisibleForTesting  static final int CLEAN_UP_UID_DELAY_MILLIS = 1000;
+    private static final int DEFAULT_GPU_HEADROOM_INTERVAL_MILLIS = 1000;
+    private static final int DEFAULT_CPU_HEADROOM_INTERVAL_MILLIS = 1000;
+    private static final int HEADROOM_INTERVAL_UNSUPPORTED = -1;
+    @VisibleForTesting static final int DEFAULT_HEADROOM_PID = -1;
 
 
     @VisibleForTesting final long mHintSessionPreferredRate;
@@ -160,10 +168,76 @@
 
     private static final String PROPERTY_SF_ENABLE_CPU_HINT = "debug.sf.enable_adpf_cpu_hint";
     private static final String PROPERTY_HWUI_ENABLE_HINT_MANAGER = "debug.hwui.use_hint_manager";
+    private static final String PROPERTY_USE_HAL_HEADROOMS = "persist.hms.use_hal_headrooms";
 
     private Boolean mFMQUsesIntegratedEventFlag = false;
 
-    @VisibleForTesting final IHintManager.Stub mService = new BinderService();
+    private final Object mCpuHeadroomLock = new Object();
+
+    private static class CpuHeadroomCacheItem {
+        long mExpiredTimeMillis;
+        CpuHeadroomParamsInternal mParams;
+        float[] mHeadroom;
+        long mPid;
+
+        CpuHeadroomCacheItem(long expiredTimeMillis, CpuHeadroomParamsInternal params,
+                float[] headroom, long pid) {
+            mExpiredTimeMillis = expiredTimeMillis;
+            mParams = params;
+            mPid = pid;
+            mHeadroom = headroom;
+        }
+
+        private boolean match(CpuHeadroomParamsInternal params, long pid) {
+            if (mParams == null && params == null) return true;
+            if (mParams != null) {
+                return mParams.equals(params) && pid == mPid;
+            }
+            return false;
+        }
+
+        private boolean isExpired() {
+            return System.currentTimeMillis() > mExpiredTimeMillis;
+        }
+    }
+
+    @GuardedBy("mCpuHeadroomLock")
+    private final List<CpuHeadroomCacheItem> mCpuHeadroomCache;
+    private final long mCpuHeadroomIntervalMillis;
+
+    private final Object mGpuHeadroomLock = new Object();
+
+    private static class GpuHeadroomCacheItem {
+        long mExpiredTimeMillis;
+        GpuHeadroomParamsInternal mParams;
+        float mHeadroom;
+
+        GpuHeadroomCacheItem(long expiredTimeMillis, GpuHeadroomParamsInternal params,
+                float headroom) {
+            mExpiredTimeMillis = expiredTimeMillis;
+            mParams = params;
+            mHeadroom = headroom;
+        }
+
+        private boolean match(GpuHeadroomParamsInternal params) {
+            if (mParams == null && params == null) return true;
+            if (mParams != null) {
+                return mParams.equals(params);
+            }
+            return false;
+        }
+
+        private boolean isExpired() {
+            return System.currentTimeMillis() > mExpiredTimeMillis;
+        }
+    }
+
+    @GuardedBy("mGpuHeadroomLock")
+    private final List<GpuHeadroomCacheItem> mGpuHeadroomCache;
+    private final long mGpuHeadroomIntervalMillis;
+
+    @VisibleForTesting
+    final IHintManager.Stub mService = new BinderService();
 
     public HintManagerService(Context context) {
         this(context, new Injector());
@@ -197,13 +271,72 @@
         mPowerHal = injector.createIPower();
         mPowerHalVersion = 0;
         mUsesFmq = false;
+        long cpuHeadroomIntervalMillis = HEADROOM_INTERVAL_UNSUPPORTED;
+        long gpuHeadroomIntervalMillis = HEADROOM_INTERVAL_UNSUPPORTED;
         if (mPowerHal != null) {
             try {
                 mPowerHalVersion = mPowerHal.getInterfaceVersion();
+                if (mPowerHal.getInterfaceVersion() >= 6) {
+                    if (SystemProperties.getBoolean(PROPERTY_USE_HAL_HEADROOMS, true)) {
+                        cpuHeadroomIntervalMillis = checkCpuHeadroomSupport();
+                        gpuHeadroomIntervalMillis = checkGpuHeadroomSupport();
+                    }
+                }
             } catch (RemoteException e) {
                 throw new IllegalStateException("Could not contact PowerHAL!", e);
             }
         }
+        mCpuHeadroomIntervalMillis = cpuHeadroomIntervalMillis;
+        mGpuHeadroomIntervalMillis = gpuHeadroomIntervalMillis;
+        if (mCpuHeadroomIntervalMillis > 0) {
+            mCpuHeadroomCache = new ArrayList<>(4);
+        } else {
+            mCpuHeadroomCache = null;
+        }
+        if (mGpuHeadroomIntervalMillis > 0) {
+            mGpuHeadroomCache = new ArrayList<>(2);
+        } else {
+            mGpuHeadroomCache = null;
+        }
+    }
+
+    private long checkCpuHeadroomSupport() {
+        try {
+            synchronized (mCpuHeadroomLock) {
+                final CpuHeadroomParams defaultParams = new CpuHeadroomParams();
+                defaultParams.pid = Process.myPid();
+                float[] ret = mPowerHal.getCpuHeadroom(defaultParams);
+                if (ret != null && ret.length > 0) {
+                    return Math.max(
+                            DEFAULT_CPU_HEADROOM_INTERVAL_MILLIS,
+                            mPowerHal.getCpuHeadroomMinIntervalMillis());
+                }
+            }
+
+        } catch (UnsupportedOperationException e) {
+            Slog.w(TAG, "getCpuHeadroom HAL API is not supported", e);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "getCpuHeadroom HAL API fails, disabling the API", e);
+        }
+        return HEADROOM_INTERVAL_UNSUPPORTED;
+    }
+
+    private long checkGpuHeadroomSupport() {
+        try {
+            synchronized (mGpuHeadroomLock) {
+                float ret = mPowerHal.getGpuHeadroom(new GpuHeadroomParams());
+                if (!Float.isNaN(ret)) {
+                    return Math.max(
+                            DEFAULT_GPU_HEADROOM_INTERVAL_MILLIS,
+                            mPowerHal.getGpuHeadroomMinIntervalMillis());
+                }
+            }
+        } catch (UnsupportedOperationException e) {
+            Slog.w(TAG, "getGpuHeadroom HAL API is not supported", e);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "getGpuHeadroom HAL API fails, disabling the API", e);
+        }
+        return HEADROOM_INTERVAL_UNSUPPORTED;
     }
 
     private ServiceThread createCleanUpThread() {
@@ -738,7 +871,7 @@
                 mLinked = false;
             }
             if (mConfig != null) {
-                try  {
+                try {
                     mPowerHal.closeSessionChannel(mTgid, mUid);
                 } catch (RemoteException e) {
                     throw new IllegalStateException("Failed to close session channel!", e);
@@ -982,13 +1115,13 @@
     }
 
     // returns the first invalid tid or null if not found
-    private Integer checkTidValid(int uid, int tgid, int [] tids, IntArray nonIsolated) {
+    private Integer checkTidValid(int uid, int tgid, int[] tids, IntArray nonIsolated) {
         // Make sure all tids belongs to the same UID (including isolated UID),
         // tids can belong to different application processes.
         List<Integer> isolatedPids = null;
         for (int i = 0; i < tids.length; i++) {
             int tid = tids[i];
-            final String[] procStatusKeys = new String[] {
+            final String[] procStatusKeys = new String[]{
                     "Uid:",
                     "Tgid:"
             };
@@ -1058,7 +1191,7 @@
                     Slogf.w(TAG, errMsg);
                     throw new SecurityException(errMsg);
                 }
-                if (resetOnForkEnabled()){
+                if (resetOnForkEnabled()) {
                     try {
                         for (int tid : tids) {
                             int policy = Process.getThreadScheduler(tid);
@@ -1214,6 +1347,124 @@
         }
 
         @Override
+        public float[] getCpuHeadroom(@Nullable CpuHeadroomParamsInternal params) {
+            if (mCpuHeadroomIntervalMillis <= 0) {
+                throw new UnsupportedOperationException();
+            }
+            CpuHeadroomParams halParams = new CpuHeadroomParams();
+            halParams.pid = Binder.getCallingPid();
+            if (params != null) {
+                halParams.calculationType = params.calculationType;
+                halParams.selectionType = params.selectionType;
+                if (params.usesDeviceHeadroom) {
+                    halParams.pid = DEFAULT_HEADROOM_PID;
+                }
+            }
+            synchronized (mCpuHeadroomLock) {
+                while (!mCpuHeadroomCache.isEmpty()) {
+                    if (mCpuHeadroomCache.getFirst().isExpired()) {
+                        mCpuHeadroomCache.removeFirst();
+                    } else {
+                        break;
+                    }
+                }
+                for (int i = 0; i < mCpuHeadroomCache.size(); ++i) {
+                    final CpuHeadroomCacheItem item = mCpuHeadroomCache.get(i);
+                    if (item.match(params, halParams.pid)) {
+                        item.mExpiredTimeMillis =
+                                System.currentTimeMillis() + mCpuHeadroomIntervalMillis;
+                        mCpuHeadroomCache.remove(i);
+                        mCpuHeadroomCache.add(item);
+                        return item.mHeadroom;
+                    }
+                }
+            }
+            // return from HAL directly
+            try {
+                float[] headroom = mPowerHal.getCpuHeadroom(halParams);
+                if (headroom == null || headroom.length == 0) {
+                    Slog.wtf(TAG,
+                            "CPU headroom from Power HAL is invalid: " + Arrays.toString(headroom));
+                    return new float[]{Float.NaN};
+                }
+                synchronized (mCpuHeadroomLock) {
+                    mCpuHeadroomCache.add(new CpuHeadroomCacheItem(
+                            System.currentTimeMillis() + mCpuHeadroomIntervalMillis,
+                            params, headroom, halParams.pid
+                    ));
+                }
+                return headroom;
+
+            } catch (RemoteException e) {
+                return new float[]{Float.NaN};
+            }
+        }
+
+        @Override
+        public float getGpuHeadroom(@Nullable GpuHeadroomParamsInternal params) {
+            if (mGpuHeadroomIntervalMillis <= 0) {
+                throw new UnsupportedOperationException();
+            }
+            GpuHeadroomParams halParams = new GpuHeadroomParams();
+            if (params != null) {
+                halParams.calculationType = params.calculationType;
+            }
+            synchronized (mGpuHeadroomLock) {
+                while (!mGpuHeadroomCache.isEmpty()) {
+                    if (mGpuHeadroomCache.getFirst().isExpired()) {
+                        mGpuHeadroomCache.removeFirst();
+                    } else {
+                        break;
+                    }
+                }
+                for (int i = 0; i < mGpuHeadroomCache.size(); ++i) {
+                    final GpuHeadroomCacheItem item = mGpuHeadroomCache.get(i);
+                    if (item.match(params)) {
+                        item.mExpiredTimeMillis =
+                                System.currentTimeMillis() + mGpuHeadroomIntervalMillis;
+                        mGpuHeadroomCache.remove(i);
+                        mGpuHeadroomCache.add(item);
+                        return item.mHeadroom;
+                    }
+                }
+            }
+            // return from HAL directly
+            try {
+                float headroom = mPowerHal.getGpuHeadroom(halParams);
+                if (Float.isNaN(headroom)) {
+                    Slog.wtf(TAG,
+                            "GPU headroom from Power HAL is NaN");
+                    return Float.NaN;
+                }
+                synchronized (mGpuHeadroomLock) {
+                    mGpuHeadroomCache.add(new GpuHeadroomCacheItem(
+                            System.currentTimeMillis() + mGpuHeadroomIntervalMillis,
+                            params, headroom
+                    ));
+                }
+                return headroom;
+            } catch (RemoteException e) {
+                return Float.NaN;
+            }
+        }
+
+        @Override
+        public long getCpuHeadroomMinIntervalMillis() throws RemoteException {
+            if (mCpuHeadroomIntervalMillis <= 0) {
+                throw new UnsupportedOperationException();
+            }
+            return mCpuHeadroomIntervalMillis;
+        }
+
+        @Override
+        public long getGpuHeadroomMinIntervalMillis() throws RemoteException {
+            if (mGpuHeadroomIntervalMillis <= 0) {
+                throw new UnsupportedOperationException();
+            }
+            return mGpuHeadroomIntervalMillis;
+        }
+
+        @Override
         public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
                 return;
@@ -1235,6 +1486,25 @@
                     }
                 }
             }
+            pw.println("CPU Headroom Interval: " + mCpuHeadroomIntervalMillis);
+            pw.println("GPU Headroom Interval: " + mGpuHeadroomIntervalMillis);
+            try {
+                CpuHeadroomParamsInternal params = new CpuHeadroomParamsInternal();
+                params.selectionType = CpuHeadroomParams.SelectionType.ALL;
+                params.usesDeviceHeadroom = true;
+                pw.println("CPU headroom: " + Arrays.toString(getCpuHeadroom(params)));
+                params = new CpuHeadroomParamsInternal();
+                params.selectionType = CpuHeadroomParams.SelectionType.PER_CORE;
+                params.usesDeviceHeadroom = true;
+                pw.println("CPU headroom per core: " + Arrays.toString(getCpuHeadroom(params)));
+            } catch (Exception e) {
+                pw.println("CPU headroom: N/A");
+            }
+            try {
+                pw.println("GPU headroom: " + getGpuHeadroom(null));
+            } catch (Exception e) {
+                pw.println("GPU headroom: N/A");
+            }
         }
 
         private void logPerformanceHintSessionAtom(int uid, long sessionId,
@@ -1467,7 +1737,7 @@
                             Slogf.w(TAG, errMsg);
                             throw new SecurityException(errMsg);
                         }
-                        if (resetOnForkEnabled()){
+                        if (resetOnForkEnabled()) {
                             try {
                                 for (int tid : tids) {
                                     int policy = Process.getThreadScheduler(tid);
diff --git a/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java
index 1260eee..e780be4 100644
--- a/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java
+++ b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java
@@ -46,6 +46,7 @@
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.security.advancedprotection.features.AdvancedProtectionHook;
 import com.android.server.security.advancedprotection.features.AdvancedProtectionProvider;
+import com.android.server.security.advancedprotection.features.DisallowInstallUnknownSourcesAdvancedProtectionHook;
 
 import java.io.FileDescriptor;
 import java.util.ArrayList;
@@ -76,10 +77,9 @@
     }
 
     private void initFeatures(boolean enabled) {
-        // Empty until features are added.
-        // Examples:
-        // mHooks.add(new SideloadingAdvancedProtectionHook(mContext, enabled));
-        // mProviders.add(new WifiAdvancedProtectionProvider());
+        if (android.security.Flags.aapmFeatureDisableInstallUnknownSources()) {
+            mHooks.add(new DisallowInstallUnknownSourcesAdvancedProtectionHook(mContext, enabled));
+        }
     }
 
     // Only for tests
diff --git a/services/core/java/com/android/server/security/advancedprotection/features/DisallowInstallUnknownSourcesAdvancedProtectionHook.java b/services/core/java/com/android/server/security/advancedprotection/features/DisallowInstallUnknownSourcesAdvancedProtectionHook.java
new file mode 100644
index 0000000..21752e5
--- /dev/null
+++ b/services/core/java/com/android/server/security/advancedprotection/features/DisallowInstallUnknownSourcesAdvancedProtectionHook.java
@@ -0,0 +1,73 @@
+/*
+ * 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.security.advancedprotection.features;
+
+import static android.security.advancedprotection.AdvancedProtectionManager.ADVANCED_PROTECTION_SYSTEM_ENTITY;
+import static android.security.advancedprotection.AdvancedProtectionManager.FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES;
+
+import android.annotation.NonNull;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.os.UserManager;
+import android.security.advancedprotection.AdvancedProtectionFeature;
+import android.util.Slog;
+
+/** @hide */
+public final class DisallowInstallUnknownSourcesAdvancedProtectionHook
+        extends AdvancedProtectionHook {
+    private static final String TAG = "AdvancedProtectionDisallowInstallUnknown";
+
+    private final AdvancedProtectionFeature mFeature = new AdvancedProtectionFeature(
+            FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES);
+    private final DevicePolicyManager mDevicePolicyManager;
+
+    public DisallowInstallUnknownSourcesAdvancedProtectionHook(@NonNull Context context,
+            boolean enabled) {
+        super(context, enabled);
+        mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
+        onAdvancedProtectionChanged(enabled);
+    }
+
+    @NonNull
+    @Override
+    public AdvancedProtectionFeature getFeature() {
+        return mFeature;
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return true;
+    }
+
+    @Override
+    public void onAdvancedProtectionChanged(boolean enabled) {
+        if (enabled) {
+            Slog.d(TAG, "Setting DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY restriction");
+            mDevicePolicyManager.addUserRestrictionGlobally(ADVANCED_PROTECTION_SYSTEM_ENTITY,
+                    UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY);
+            return;
+        }
+        Slog.d(TAG, "Clearing DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY restriction");
+        mDevicePolicyManager.clearUserRestrictionGlobally(ADVANCED_PROTECTION_SYSTEM_ENTITY,
+                UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY);
+
+        // TODO(b/369361373):
+        //  1. After clearing the restriction, set AppOpsManager.OP_REQUEST_INSTALL_PACKAGES to
+        //  disabled.
+        //  2. Update dialog strings.
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
index 42203b1..07d9ad1 100644
--- a/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/AbstractVibratorStep.java
@@ -112,6 +112,14 @@
     }
 
     protected void stopVibrating() {
+        if (conductor.isInSession) {
+            if (VibrationThread.DEBUG) {
+                Slog.d(VibrationThread.TAG,
+                        "Vibration in session, skipping request to turn off vibrator "
+                                + getVibratorId());
+            }
+            return;
+        }
         if (VibrationThread.DEBUG) {
             Slog.d(VibrationThread.TAG,
                     "Turning off vibrator " + getVibratorId());
diff --git a/services/core/java/com/android/server/vibrator/SplitPwleSegmentsAdapter.java b/services/core/java/com/android/server/vibrator/SplitPwleSegmentsAdapter.java
index ad44227..a8c4ac8 100644
--- a/services/core/java/com/android/server/vibrator/SplitPwleSegmentsAdapter.java
+++ b/services/core/java/com/android/server/vibrator/SplitPwleSegmentsAdapter.java
@@ -44,7 +44,7 @@
             // The vibrator does not have PWLE v2 capability, so keep the segments unchanged.
             return repeatIndex;
         }
-        int maxPwleDuration = info.getMaxEnvelopeEffectDurationMillis();
+        int maxPwleDuration = (int) info.getMaxEnvelopeEffectDurationMillis();
         if (maxPwleDuration <= 0) {
             // No limit set to PWLE primitive duration.
             return repeatIndex;
diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
index 6a4790d..1e20deb 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
@@ -69,6 +69,7 @@
     // Used within steps.
     public final VibrationSettings vibrationSettings;
     public final VibrationThread.VibratorManagerHooks vibratorManagerHooks;
+    public final boolean isInSession;
 
     private final DeviceAdapter mDeviceAdapter;
     private final VibrationScaler mVibrationScaler;
@@ -105,12 +106,13 @@
     private int mRemainingStartSequentialEffectSteps;
     private int mSuccessfulVibratorOnSteps;
 
-    VibrationStepConductor(HalVibration vib, VibrationSettings vibrationSettings,
-            DeviceAdapter deviceAdapter, VibrationScaler vibrationScaler,
-            VibratorFrameworkStatsLogger statsLogger,
+    VibrationStepConductor(HalVibration vib, boolean isInSession,
+            VibrationSettings vibrationSettings, DeviceAdapter deviceAdapter,
+            VibrationScaler vibrationScaler, VibratorFrameworkStatsLogger statsLogger,
             CompletableFuture<Void> requestVibrationParamsFuture,
             VibrationThread.VibratorManagerHooks vibratorManagerHooks) {
         this.mVibration = vib;
+        this.isInSession = isInSession;
         this.vibrationSettings = vibrationSettings;
         this.mDeviceAdapter = deviceAdapter;
         mVibrationScaler = vibrationScaler;
@@ -286,6 +288,9 @@
         if (nextStep == null) {
             return true;  // Finished
         }
+        if (isInSession) {
+            return true;  // Don't wait to play session vibration steps
+        }
         long waitMillis = nextStep.calculateWaitTime();
         if (waitMillis <= 0) {
             return true;  // Regular step ready
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 4764481..1030df6 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -1114,8 +1114,9 @@
                             mVibrationSettings.getRequestVibrationParamsTimeoutMs());
         }
 
-        return new VibrationStepConductor(vib, mVibrationSettings, mDeviceAdapter, mVibrationScaler,
-                mFrameworkStatsLogger, requestVibrationParamsFuture, mVibrationThreadCallbacks);
+        return new VibrationStepConductor(vib, /* isInSession= */ false, mVibrationSettings,
+                mDeviceAdapter, mVibrationScaler, mFrameworkStatsLogger,
+                requestVibrationParamsFuture, mVibrationThreadCallbacks);
     }
 
     private Status startVibrationOnInputDevicesLocked(HalVibration vib) {
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 4b2d454..cf145f9 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -228,13 +228,11 @@
             changed |= provider.updateClientVisibility(caller,
                     isImeProvider ? statsToken : null);
         }
-        if (!android.view.inputmethod.Flags.refactorInsetsController()) {
-            if (changed) {
-                notifyInsetsChanged();
-                mDisplayContent.updateSystemGestureExclusion();
+        if (changed) {
+            notifyInsetsChanged();
+            mDisplayContent.updateSystemGestureExclusion();
 
-                mDisplayContent.getDisplayPolicy().updateSystemBarAttributes();
-            }
+            mDisplayContent.getDisplayPolicy().updateSystemBarAttributes();
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 9de96f14..81a04af 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -326,9 +326,12 @@
         ProtoLog.i(WM_DEBUG_TASKS, "Setting frozen recents task list");
 
         // Always update the reordering time when this is called to ensure that the timeout
-        // is reset
+        // is reset.  Extend this duration when running in tests.
+        final long timeout = ActivityManager.isRunningInUserTestHarness()
+                ? mFreezeTaskListTimeoutMs * 10
+                : mFreezeTaskListTimeoutMs;
         mService.mH.removeCallbacks(mResetFreezeTaskListOnTimeoutRunnable);
-        mService.mH.postDelayed(mResetFreezeTaskListOnTimeoutRunnable, mFreezeTaskListTimeoutMs);
+        mService.mH.postDelayed(mResetFreezeTaskListOnTimeoutRunnable, timeout);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 88b2d22..54b257c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -10233,6 +10233,17 @@
         }
     }
 
+    /**
+     * Resets the spatial ordering of recents for testing purposes.
+     */
+    void resetFreezeRecentTaskListReordering() {
+        if (!checkCallingPermission(permission.MANAGE_ACTIVITY_TASKS,
+                "resetFreezeRecentTaskListReordering()")) {
+            throw new SecurityException("Requires MANAGE_ACTIVITY_TASKS permission");
+        }
+        mAtmService.getRecentTasks().resetFreezeTaskListReorderingOnTimeout();
+    }
+
     @Override
     public void registerTrustedPresentationListener(IBinder window,
             ITrustedPresentationListener listener,
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 21ed8d7..fe2bcc7 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -161,6 +161,8 @@
                     return runReset(pw);
                 case "disable-blur":
                     return runSetBlurDisabled(pw);
+                case "reset-freeze-recent-tasks":
+                    return runResetFreezeRecentTaskListReordering(pw);
                 case "set-display-windowing-mode":
                     return runSetDisplayWindowingMode(pw);
                 case "get-display-windowing-mode":
@@ -275,6 +277,11 @@
         return 0;
     }
 
+    private int runResetFreezeRecentTaskListReordering(PrintWriter pw) throws RemoteException {
+        mInternal.resetFreezeRecentTaskListReordering();
+        return 0;
+    }
+
     private void printInitialDisplayDensity(PrintWriter pw , int displayId) {
         try {
             final int initialDensity = mInterface.getInitialDisplayDensity(displayId);
@@ -1592,6 +1599,8 @@
         printLetterboxHelp(pw);
         printMultiWindowConfigHelp(pw);
 
+        pw.println("  reset-freeze-recent-tasks");
+        pw.println("    Resets the spatial ordering of the recent tasks list");
         pw.println("  set-display-windowing-mode [-d DISPLAY_ID] [mode_id]");
         pw.println("    As mode_id, use " + WINDOWING_MODE_UNDEFINED + " for undefined, "
                 + WINDOWING_MODE_FREEFORM + " for freeform, " + WINDOWING_MODE_FULLSCREEN + " for"
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index c19c58e..cb333f0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -1236,6 +1236,8 @@
                 }
             }
             for (EnforcingAdmin admin : admins) {
+                // No need to make changes to system enforcing admins.
+                if (admin.isSystemAuthority()) break;
                 if (updatedPackage == null || updatedPackage.equals(admin.getPackageName())) {
                     if (!isPackageInstalled(admin.getPackageName(), userId)) {
                         Slogf.i(TAG, String.format(
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 8ad8786..28eec5c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -13362,10 +13362,13 @@
         Objects.requireNonNull(systemEntity);
 
         CallerIdentity caller = getCallerIdentity();
-        if (caller.getUid() != Process.SYSTEM_UID) {
+        if (!isSystemUid(caller)) {
             throw new SecurityException("Only system services can call setUserRestrictionForUser"
                     + " on a target user: " + targetUser);
         }
+        if (!UserRestrictionsUtils.isValidRestriction(key)) {
+            throw new IllegalArgumentException("Invalid restriction key: " + key);
+        }
         if (VERBOSE_LOG) {
             Slogf.v(LOG_TAG, "Creating SystemEnforcingAdmin %s for calling package %s",
                     systemEntity, caller.getPackageName());
@@ -13498,6 +13501,31 @@
         logUserRestrictionCall(key, /* enabled= */ true, /* parent= */ false, caller,
                 UserHandle.USER_ALL);
     }
+
+    @Override
+    public void setUserRestrictionGloballyFromSystem(@NonNull String systemEntity, String key,
+            boolean enabled) {
+        Objects.requireNonNull(systemEntity);
+
+        CallerIdentity caller = getCallerIdentity();
+        if (!isSystemUid(caller)) {
+            throw new SecurityException("Only system services can call"
+                    + " setUserRestrictionGloballyFromSystem");
+        }
+        if (!UserRestrictionsUtils.isValidRestriction(key)) {
+            throw new IllegalArgumentException("Invalid restriction key: " + key);
+        }
+        if (VERBOSE_LOG) {
+            Slogf.v(LOG_TAG, "Creating SystemEnforcingAdmin %s for calling package %s",
+                    systemEntity, caller.getPackageName());
+        }
+        EnforcingAdmin admin = EnforcingAdmin.createSystemEnforcingAdmin(systemEntity);
+
+        setGlobalUserRestrictionInternal(admin, key, enabled);
+
+        logUserRestrictionCall(key, enabled, /* parent= */ false, caller, UserHandle.USER_ALL);
+    }
+
     private void setLocalUserRestrictionInternal(
             EnforcingAdmin admin, String key, boolean enabled, int userId) {
         PolicyDefinition<Boolean> policyDefinition =
@@ -13515,6 +13543,7 @@
                     userId);
         }
     }
+
     private void setGlobalUserRestrictionInternal(
             EnforcingAdmin admin, String key, boolean enabled) {
         PolicyDefinition<Boolean> policyDefinition =
@@ -19119,6 +19148,14 @@
         }
     }
 
+    private boolean isAnyResetPasswordTokenActiveForUser(int userId) {
+        return mDevicePolicyEngine
+                .getLocalPoliciesSetByAdmins(PolicyDefinition.RESET_PASSWORD_TOKEN, userId)
+                .values()
+                .stream()
+                .anyMatch((p) -> isResetPasswordTokenActiveForUserLocked(p.getValue(), userId));
+    }
+
     private boolean isResetPasswordTokenActiveForUserLocked(
             long passwordTokenHandle, int userHandle) {
         if (passwordTokenHandle != 0) {
@@ -20974,6 +21011,9 @@
         Preconditions.checkCallAuthorization(isSystemUid(getCallerIdentity()),
                 String.format(NOT_SYSTEM_CALLER_MSG,
                         "call canProfileOwnerResetPasswordWhenLocked"));
+        if (Flags.resetPasswordWithTokenCoexistence()) {
+            return isAnyResetPasswordTokenActiveForUser(userId);
+        }
         synchronized (getLockObject()) {
             final ActiveAdmin poAdmin = getProfileOwnerAdminLocked(userId);
             DevicePolicyData policy = getUserData(userId);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
index 634f1bc..58e3a7d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
@@ -280,6 +280,10 @@
         return getAuthorities().contains(authority);
     }
 
+    boolean isSystemAuthority() {
+        return mIsSystemAuthority;
+    }
+
     @NonNull
     String getPackageName() {
         return mPackageName;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 80e5ee3..759976f 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -226,6 +226,9 @@
             "EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED";
     private static final String EVENT_DISPLAY_CONNECTED = "EVENT_DISPLAY_CONNECTED";
     private static final String EVENT_DISPLAY_DISCONNECTED = "EVENT_DISPLAY_DISCONNECTED";
+    private static final String EVENT_DISPLAY_REFRESH_RATE_CHANGED =
+            "EVENT_DISPLAY_REFRESH_RATE_CHANGED";
+    private static final String EVENT_DISPLAY_STATE_CHANGED = "EVENT_DISPLAY_STATE_CHANGED";
     private static final String DISPLAY_GROUP_EVENT_ADDED = "DISPLAY_GROUP_EVENT_ADDED";
     private static final String DISPLAY_GROUP_EVENT_REMOVED = "DISPLAY_GROUP_EVENT_REMOVED";
     private static final String DISPLAY_GROUP_EVENT_CHANGED = "DISPLAY_GROUP_EVENT_CHANGED";
@@ -4234,6 +4237,10 @@
                     return EVENT_DISPLAY_CONNECTED;
                 case DisplayManagerGlobal.EVENT_DISPLAY_DISCONNECTED:
                     return EVENT_DISPLAY_DISCONNECTED;
+                case DisplayManagerGlobal.EVENT_DISPLAY_REFRESH_RATE_CHANGED:
+                    return EVENT_DISPLAY_REFRESH_RATE_CHANGED;
+                case DisplayManagerGlobal.EVENT_DISPLAY_STATE_CHANGED:
+                    return EVENT_DISPLAY_STATE_CHANGED;
                 default:
                     return "UNKNOWN: " + eventType;
             }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
index b6da3ae..ff652a2 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -35,7 +35,9 @@
 import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED;
 import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_CONNECTED;
 import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_DISCONNECTED;
+import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED;
 import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED;
+import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_STATE_CHANGED;
 import static com.android.server.display.layout.Layout.Display.POSITION_REAR;
 import static com.android.server.display.layout.Layout.Display.POSITION_UNKNOWN;
 import static com.android.server.utils.FoldSettingProvider.SETTING_VALUE_SELECTIVE_STAY_AWAKE;
@@ -1158,6 +1160,29 @@
                 mLogicalDisplayMapper.getDisplayLocked(device2).getDevicePositionLocked());
     }
 
+    @Test
+    public void updateAndGetMaskForDisplayPropertyChanges_getsPropertyChangedFlags() {
+        // Change the display state
+        DisplayInfo newDisplayInfo = new DisplayInfo();
+        newDisplayInfo.state = STATE_OFF;
+        assertEquals(LOGICAL_DISPLAY_EVENT_STATE_CHANGED,
+                mLogicalDisplayMapper.updateAndGetMaskForDisplayPropertyChanges(newDisplayInfo));
+
+        // Change the refresh rate override
+        newDisplayInfo = new DisplayInfo();
+        newDisplayInfo.refreshRateOverride = 30;
+        assertEquals(LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED,
+                mLogicalDisplayMapper.updateAndGetMaskForDisplayPropertyChanges(newDisplayInfo));
+
+        // Change multiple properties
+        newDisplayInfo = new DisplayInfo();
+        newDisplayInfo.refreshRateOverride = 30;
+        newDisplayInfo.state = STATE_OFF;
+        assertEquals(LOGICAL_DISPLAY_EVENT_REFRESH_RATE_CHANGED
+                        | LOGICAL_DISPLAY_EVENT_STATE_CHANGED,
+                mLogicalDisplayMapper.updateAndGetMaskForDisplayPropertyChanges(newDisplayInfo));
+    }
+
     /////////////////
     // Helper Methods
     /////////////////
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 95acd75..993569f 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -78,7 +78,7 @@
         "am_flags_lib",
         "device_policy_aconfig_flags_lib",
     ] + select(soong_config_variable("ANDROID", "release_crashrecovery_module"), {
-        "true": ["service-crashrecovery.impl"],
+        "true": ["service-crashrecovery-pre-jarjar"],
         default: [],
     }),
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 30de0e8..8dc8c14 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -67,7 +67,6 @@
 import static com.android.server.alarm.AlarmManagerService.AlarmHandler.CHECK_EXACT_ALARM_PERMISSION_ON_UPDATE;
 import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REFRESH_EXACT_ALARM_CANDIDATES;
 import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_EXACT_ALARMS;
-import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED;
 import static com.android.server.alarm.AlarmManagerService.AlarmHandler.REMOVE_FOR_CANCELED;
 import static com.android.server.alarm.AlarmManagerService.AlarmHandler.TEMPORARY_QUOTA_CHANGED;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_COMPAT_QUOTA;
@@ -152,7 +151,6 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.platform.test.flag.util.FlagSetException;
@@ -436,8 +434,7 @@
      */
     private void disableFlagsNotSetByAnnotation() {
         try {
-            mSetFlagsRule.disableFlags(Flags.FLAG_USE_FROZEN_STATE_TO_DROP_LISTENER_ALARMS,
-                    Flags.FLAG_START_USER_BEFORE_SCHEDULED_ALARMS);
+            mSetFlagsRule.disableFlags(Flags.FLAG_START_USER_BEFORE_SCHEDULED_ALARMS);
         } catch (FlagSetException fse) {
             // Expected if the test about to be run requires this enabled.
         }
@@ -523,13 +520,11 @@
 
         mService.onStart();
 
-        if (Flags.useFrozenStateToDropListenerAlarms()) {
-            final ArgumentCaptor<ActivityManager.UidFrozenStateChangedCallback> frozenCaptor =
-                    ArgumentCaptor.forClass(ActivityManager.UidFrozenStateChangedCallback.class);
-            verify(mActivityManager).registerUidFrozenStateChangedCallback(
-                    any(HandlerExecutor.class), frozenCaptor.capture());
-            mUidFrozenStateCallback = frozenCaptor.getValue();
-        }
+        final ArgumentCaptor<ActivityManager.UidFrozenStateChangedCallback> frozenCaptor =
+                ArgumentCaptor.forClass(ActivityManager.UidFrozenStateChangedCallback.class);
+        verify(mActivityManager).registerUidFrozenStateChangedCallback(
+                any(HandlerExecutor.class), frozenCaptor.capture());
+        mUidFrozenStateCallback = frozenCaptor.getValue();
 
         // Unable to mock mMockContext to return a mock stats manager.
         // So just mocking the whole MetricsHelper instance.
@@ -3744,79 +3739,11 @@
         testTemporaryQuota_bumpedBeforeDeferral(STANDBY_BUCKET_RARE);
     }
 
-    @Test
-    public void exactListenerAlarmsRemovedOnCached() {
-        mockChangeEnabled(EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED, true);
-
-        setTestAlarmWithListener(ELAPSED_REALTIME, 31, getNewListener(() -> {}), WINDOW_EXACT,
-                TEST_CALLING_UID);
-        setTestAlarmWithListener(RTC, 42, getNewListener(() -> {}), 56, TEST_CALLING_UID);
-        setTestAlarm(ELAPSED_REALTIME, 54, WINDOW_EXACT, getNewMockPendingIntent(), 0, 0,
-                TEST_CALLING_UID, null);
-        setTestAlarm(RTC, 49, 154, getNewMockPendingIntent(), 0, 0, TEST_CALLING_UID, null);
-
-        setTestAlarmWithListener(ELAPSED_REALTIME, 21, getNewListener(() -> {}), WINDOW_EXACT,
-                TEST_CALLING_UID_2);
-        setTestAlarmWithListener(RTC, 412, getNewListener(() -> {}), 561, TEST_CALLING_UID_2);
-        setTestAlarm(ELAPSED_REALTIME, 26, WINDOW_EXACT, getNewMockPendingIntent(), 0, 0,
-                TEST_CALLING_UID_2, null);
-        setTestAlarm(RTC, 549, 234, getNewMockPendingIntent(), 0, 0, TEST_CALLING_UID_2, null);
-
-        assertEquals(8, mService.mAlarmStore.size());
-
-        mListener.handleUidCachedChanged(TEST_CALLING_UID, true);
-        assertAndHandleMessageSync(REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED);
-        assertEquals(7, mService.mAlarmStore.size());
-
-        mListener.handleUidCachedChanged(TEST_CALLING_UID_2, true);
-        assertAndHandleMessageSync(REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED);
-        assertEquals(6, mService.mAlarmStore.size());
-    }
-
-    @Test
-    public void alarmCountOnListenerCached() {
-        mockChangeEnabled(EXACT_LISTENER_ALARMS_DROPPED_ON_CACHED, true);
-
-        // Set some alarms for TEST_CALLING_UID.
-        final int numExactListenerUid1 = 14;
-        for (int i = 0; i < numExactListenerUid1; i++) {
-            setTestAlarmWithListener(ALARM_TYPES[i % 4], mNowElapsedTest + i,
-                    getNewListener(() -> {}));
-        }
-        setTestAlarmWithListener(RTC, 42, getNewListener(() -> {}), 56, TEST_CALLING_UID);
-        setTestAlarm(ELAPSED_REALTIME, 54, getNewMockPendingIntent());
-        setTestAlarm(RTC, 49, 154, getNewMockPendingIntent(), 0, 0, TEST_CALLING_UID, null);
-
-        // Set some alarms for TEST_CALLING_UID_2.
-        final int numExactListenerUid2 = 9;
-        for (int i = 0; i < numExactListenerUid2; i++) {
-            setTestAlarmWithListener(ALARM_TYPES[i % 4], mNowElapsedTest + i,
-                    getNewListener(() -> {}), WINDOW_EXACT, TEST_CALLING_UID_2);
-        }
-        setTestAlarmWithListener(RTC, 412, getNewListener(() -> {}), 561, TEST_CALLING_UID_2);
-        setTestAlarm(RTC_WAKEUP, 26, WINDOW_EXACT, getNewMockPendingIntent(), 0, 0,
-                TEST_CALLING_UID_2, null);
-
-        assertEquals(numExactListenerUid1 + 3, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
-        assertEquals(numExactListenerUid2 + 2, mService.mAlarmsPerUid.get(TEST_CALLING_UID_2));
-
-        mListener.handleUidCachedChanged(TEST_CALLING_UID, true);
-        assertAndHandleMessageSync(REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED);
-        assertEquals(3, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
-        assertEquals(numExactListenerUid2 + 2, mService.mAlarmsPerUid.get(TEST_CALLING_UID_2));
-
-        mListener.handleUidCachedChanged(TEST_CALLING_UID_2, true);
-        assertAndHandleMessageSync(REMOVE_EXACT_LISTENER_ALARMS_ON_CACHED);
-        assertEquals(3, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
-        assertEquals(2, mService.mAlarmsPerUid.get(TEST_CALLING_UID_2));
-    }
-
     private void executeUidFrozenStateCallback(int[] uids, int[] frozenStates) {
         assertNotNull(mUidFrozenStateCallback);
         mUidFrozenStateCallback.onUidFrozenStateChanged(uids, frozenStates);
     }
 
-    @EnableFlags(Flags.FLAG_USE_FROZEN_STATE_TO_DROP_LISTENER_ALARMS)
     @DisableFlags(Flags.FLAG_START_USER_BEFORE_SCHEDULED_ALARMS)
     @Test
     public void exactListenerAlarmsRemovedOnFrozen() {
@@ -3848,7 +3775,6 @@
         assertEquals(6, mService.mAlarmStore.size());
     }
 
-    @EnableFlags(Flags.FLAG_USE_FROZEN_STATE_TO_DROP_LISTENER_ALARMS)
     @DisableFlags(Flags.FLAG_START_USER_BEFORE_SCHEDULED_ALARMS)
     @Test
     public void alarmCountOnListenerFrozen() {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
index a1a8b0e..c1e71d2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
@@ -186,9 +186,9 @@
         doReturn(mAppStartInfoTracker).when(mProcessList).getAppStartInfoTracker();
 
         doReturn(true).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
-                eq(BroadcastFilter.CHANGE_RESTRICT_PRIORITY_VALUES), anyInt());
+                eq(BroadcastFilter.RESTRICT_PRIORITY_VALUES), anyInt());
         doReturn(true).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
-                eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE), anyInt());
+                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), anyInt());
     }
 
     public void tearDown() throws Exception {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastFilterTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastFilterTest.java
index e977a7d..353dc2d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastFilterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastFilterTest.java
@@ -59,7 +59,7 @@
     @EnableFlags(Flags.FLAG_RESTRICT_PRIORITY_VALUES)
     public void testCalculateAdjustedPriority() {
         doReturn(true).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
-                eq(BroadcastFilter.CHANGE_RESTRICT_PRIORITY_VALUES), anyInt());
+                eq(BroadcastFilter.RESTRICT_PRIORITY_VALUES), anyInt());
 
         {
             // Pairs of {initial-priority, expected-adjusted-priority}
@@ -96,7 +96,7 @@
     @EnableFlags(Flags.FLAG_RESTRICT_PRIORITY_VALUES)
     public void testCalculateAdjustedPriority_withChangeIdDisabled() {
         doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
-                eq(BroadcastFilter.CHANGE_RESTRICT_PRIORITY_VALUES), anyInt());
+                eq(BroadcastFilter.RESTRICT_PRIORITY_VALUES), anyInt());
 
         {
             // Pairs of {initial-priority, expected-adjusted-priority}
@@ -133,7 +133,7 @@
     @DisableFlags(Flags.FLAG_RESTRICT_PRIORITY_VALUES)
     public void testCalculateAdjustedPriority_withFlagDisabled() {
         doReturn(true).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
-                eq(BroadcastFilter.CHANGE_RESTRICT_PRIORITY_VALUES), anyInt());
+                eq(BroadcastFilter.RESTRICT_PRIORITY_VALUES), anyInt());
 
         {
             // Pairs of {initial-priority, expected-adjusted-priority}
@@ -170,7 +170,7 @@
     @DisableFlags(Flags.FLAG_RESTRICT_PRIORITY_VALUES)
     public void testCalculateAdjustedPriority_withFlagDisabled_withChangeIdDisabled() {
         doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
-                eq(BroadcastFilter.CHANGE_RESTRICT_PRIORITY_VALUES), anyInt());
+                eq(BroadcastFilter.RESTRICT_PRIORITY_VALUES), anyInt());
 
         {
             // Pairs of {initial-priority, expected-adjusted-priority}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 1481085..88caaa6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -717,7 +717,7 @@
     @Test
     public void testRunnableAt_Cached_Prioritized_NonDeferrable_changeIdDisabled() {
         doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
-                eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE),
+                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE),
                 eq(getUidForPackage(PACKAGE_GREEN)));
         final List receivers = List.of(
                 withPriority(makeManifestReceiver(PACKAGE_RED, PACKAGE_RED), 10),
@@ -1289,7 +1289,7 @@
     @Test
     public void testDeliveryGroupPolicy_prioritized_diffReceivers_changeIdDisabled() {
         doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
-                eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE),
+                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE),
                 eq(getUidForPackage(PACKAGE_GREEN)));
 
         final Intent screenOn = new Intent(Intent.ACTION_SCREEN_ON);
@@ -1824,7 +1824,7 @@
     @Test
     public void testDeliveryDeferredForCached_changeIdDisabled() throws Exception {
         doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
-                eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE),
+                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE),
                 eq(getUidForPackage(PACKAGE_GREEN)));
 
         final ProcessRecord greenProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_GREEN));
@@ -2028,7 +2028,7 @@
     public void testDeliveryDeferredForCached_withInfiniteDeferred_changeIdDisabled()
             throws Exception {
         doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
-                eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE),
+                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE),
                 eq(getUidForPackage(PACKAGE_GREEN)));
         final ProcessRecord greenProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_GREEN));
         final ProcessRecord redProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_RED));
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 9d92d5f..a38ef78 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -1660,7 +1660,7 @@
         final ProcessRecord receiverYellowApp = makeActiveProcessRecord(PACKAGE_YELLOW);
 
         doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
-                eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE), eq(receiverBlueApp.uid));
+                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), eq(receiverBlueApp.uid));
 
         // Enqueue a normal broadcast that will go to several processes, and
         // then enqueue a foreground broadcast that risks reordering
@@ -2472,7 +2472,7 @@
         final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
 
         doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
-                eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE), eq(receiverBlueApp.uid));
+                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), eq(receiverBlueApp.uid));
 
         mUidObserver.onUidStateChanged(receiverGreenApp.info.uid,
                 ActivityManager.PROCESS_STATE_TOP, 0, ActivityManager.PROCESS_CAPABILITY_NONE);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
index a424bfdb..f9f3790 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -19,7 +19,7 @@
 import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.server.am.BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE;
+import static com.android.server.am.BroadcastRecord.LIMIT_PRIORITY_SCOPE;
 import static com.android.server.am.BroadcastRecord.DELIVERY_DEFERRED;
 import static com.android.server.am.BroadcastRecord.DELIVERY_DELIVERED;
 import static com.android.server.am.BroadcastRecord.DELIVERY_PENDING;
@@ -109,7 +109,7 @@
         MockitoAnnotations.initMocks(this);
 
         doReturn(true).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
-                eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE), anyInt());
+                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), anyInt());
     }
 
     @Test
@@ -223,7 +223,7 @@
     @Test
     public void testIsPrioritized_withDifferentPriorities_withFirstUidChangeIdDisabled() {
         doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
-                eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE), eq(getAppId(1)));
+                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), eq(getAppId(1)));
 
         assertTrue(isPrioritized(List.of(
                 createResolveInfo(PACKAGE1, getAppId(1), 10),
@@ -257,7 +257,7 @@
     @Test
     public void testIsPrioritized_withDifferentPriorities_withLastUidChangeIdDisabled() {
         doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
-                eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE), eq(getAppId(3)));
+                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), eq(getAppId(3)));
 
         assertTrue(isPrioritized(List.of(
                 createResolveInfo(PACKAGE1, getAppId(1), 10),
@@ -295,7 +295,7 @@
     @Test
     public void testIsPrioritized_withDifferentPriorities_withUidChangeIdDisabled() {
         doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
-                eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE), eq(getAppId(2)));
+                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), eq(getAppId(2)));
 
         assertTrue(isPrioritized(List.of(
                 createResolveInfo(PACKAGE1, getAppId(1), 10),
@@ -329,9 +329,9 @@
     @Test
     public void testIsPrioritized_withDifferentPriorities_withMultipleUidChangeIdDisabled() {
         doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
-                eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE), eq(getAppId(1)));
+                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), eq(getAppId(1)));
         doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
-                eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE), eq(getAppId(2)));
+                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), eq(getAppId(2)));
 
         assertTrue(isPrioritized(List.of(
                 createResolveInfo(PACKAGE1, getAppId(1), 10),
@@ -593,7 +593,7 @@
     @Test
     public void testSetDeliveryState_DeferUntilActive_changeIdDisabled() {
         doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
-                eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE), eq(getAppId(1)));
+                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), eq(getAppId(1)));
         final BroadcastRecord r = createBroadcastRecord(
                 new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of(
                         createResolveInfo(PACKAGE1, getAppId(1), 10),
@@ -961,7 +961,7 @@
                         createResolveInfo(PACKAGE3, getAppId(3)))));
 
         doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
-                eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE), eq(getAppId(1)));
+                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), eq(getAppId(1)));
         assertArrayEquals(new boolean[] {false, true, true}, calculateChangeState(
                 List.of(createResolveInfo(PACKAGE1, getAppId(1)),
                         createResolveInfo(PACKAGE2, getAppId(2)),
@@ -973,7 +973,7 @@
                         createResolveInfo(PACKAGE3, getAppId(3)))));
 
         doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
-                eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE), eq(getAppId(2)));
+                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), eq(getAppId(2)));
         assertArrayEquals(new boolean[] {false, false, true}, calculateChangeState(
                 List.of(createResolveInfo(PACKAGE1, getAppId(1)),
                         createResolveInfo(PACKAGE2, getAppId(2)),
@@ -988,7 +988,7 @@
                                 createResolveInfo(PACKAGE3, getAppId(3)))));
 
         doReturn(false).when(mPlatformCompat).isChangeEnabledByUidInternalNoLogging(
-                eq(BroadcastRecord.CHANGE_LIMIT_PRIORITY_SCOPE), eq(getAppId(3)));
+                eq(BroadcastRecord.LIMIT_PRIORITY_SCOPE), eq(getAppId(3)));
         assertArrayEquals(new boolean[] {false, false, false}, calculateChangeState(
                 List.of(createResolveInfo(PACKAGE1, getAppId(1)),
                         createResolveInfo(PACKAGE2, getAppId(2)),
@@ -1005,7 +1005,7 @@
 
     private boolean[] calculateChangeState(List<Object> receivers) {
         return BroadcastRecord.calculateChangeStateForReceivers(receivers,
-                CHANGE_LIMIT_PRIORITY_SCOPE, mPlatformCompat);
+                LIMIT_PRIORITY_SCOPE, mPlatformCompat);
     }
 
     private static void cleanupDisabledPackageReceivers(BroadcastRecord record,
diff --git a/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp
index 1f88c29..8eae9c7d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp
+++ b/services/tests/mockingservicestests/src/com/android/server/crashrecovery/Android.bp
@@ -37,7 +37,7 @@
         "truth",
         "flag-junit",
     ] + select(soong_config_variable("ANDROID", "release_crashrecovery_module"), {
-        "true": ["service-crashrecovery.impl"],
+        "true": ["service-crashrecovery-pre-jarjar"],
         default: [],
     }),
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp b/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp
index 2f23e02..5a802d9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp
@@ -35,7 +35,7 @@
         "truth",
         "flag-junit",
     ] + select(soong_config_variable("ANDROID", "release_crashrecovery_module"), {
-        "true": ["service-crashrecovery.impl"],
+        "true": ["service-crashrecovery-pre-jarjar"],
         default: [],
     }),
 
diff --git a/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java
index 58489f3..0881b4c 100644
--- a/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java
+++ b/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java
@@ -18,6 +18,7 @@
 
 
 import static com.android.server.power.hint.HintManagerService.CLEAN_UP_UID_DELAY_MILLIS;
+import static com.android.server.power.hint.HintManagerService.DEFAULT_HEADROOM_PID;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -51,11 +52,15 @@
 import android.hardware.common.fmq.MQDescriptor;
 import android.hardware.power.ChannelConfig;
 import android.hardware.power.ChannelMessage;
+import android.hardware.power.CpuHeadroomParams;
+import android.hardware.power.GpuHeadroomParams;
 import android.hardware.power.IPower;
 import android.hardware.power.SessionConfig;
 import android.hardware.power.SessionTag;
 import android.hardware.power.WorkDuration;
 import android.os.Binder;
+import android.os.CpuHeadroomParamsInternal;
+import android.os.GpuHeadroomParamsInternal;
 import android.os.IBinder;
 import android.os.IHintSession;
 import android.os.PerformanceHintManager;
@@ -128,11 +133,11 @@
     private static final long[] TIMESTAMPS_ZERO = new long[] {};
     private static final long[] TIMESTAMPS_TWO = new long[] {1L, 2L};
     private static final WorkDuration[] WORK_DURATIONS_FIVE = new WorkDuration[] {
-        makeWorkDuration(1L, 11L, 1L, 8L, 4L),
-        makeWorkDuration(2L, 13L, 2L, 8L, 6L),
-        makeWorkDuration(3L, 333333333L, 3L, 8L, 333333333L),
-        makeWorkDuration(2L, 13L, 2L, 0L, 6L),
-        makeWorkDuration(2L, 13L, 2L, 8L, 0L),
+            makeWorkDuration(1L, 11L, 1L, 8L, 4L),
+            makeWorkDuration(2L, 13L, 2L, 8L, 6L),
+            makeWorkDuration(3L, 333333333L, 3L, 8L, 333333333L),
+            makeWorkDuration(2L, 13L, 2L, 0L, 6L),
+            makeWorkDuration(2L, 13L, 2L, 8L, 0L),
     };
     private static final String TEST_APP_NAME = "com.android.test.app";
 
@@ -187,17 +192,17 @@
         when(mNativeWrapperMock.halCreateHintSessionWithConfig(eq(TGID), eq(UID),
                 eq(SESSION_TIDS_A), eq(DEFAULT_TARGET_DURATION), anyInt(),
                 any(SessionConfig.class))).thenAnswer(fakeCreateWithConfig(SESSION_PTRS[0],
-                    SESSION_IDS[0]));
+                SESSION_IDS[0]));
         when(mNativeWrapperMock.halCreateHintSessionWithConfig(eq(TGID), eq(UID),
                 eq(SESSION_TIDS_B), eq(DOUBLED_TARGET_DURATION), anyInt(),
                 any(SessionConfig.class))).thenAnswer(fakeCreateWithConfig(SESSION_PTRS[1],
-                    SESSION_IDS[1]));
+                SESSION_IDS[1]));
         when(mNativeWrapperMock.halCreateHintSessionWithConfig(eq(TGID), eq(UID),
                 eq(SESSION_TIDS_C), eq(0L), anyInt(),
                 any(SessionConfig.class))).thenAnswer(fakeCreateWithConfig(SESSION_PTRS[2],
-                    SESSION_IDS[2]));
+                SESSION_IDS[2]));
 
-        when(mIPowerMock.getInterfaceVersion()).thenReturn(5);
+        when(mIPowerMock.getInterfaceVersion()).thenReturn(6);
         when(mIPowerMock.getSessionChannel(anyInt(), anyInt())).thenReturn(mConfig);
         LocalServices.removeServiceForTest(ActivityManagerInternal.class);
         LocalServices.addService(ActivityManagerInternal.class, mAmInternalMock);
@@ -217,8 +222,8 @@
         when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_C),
                 eq(0L))).thenReturn(SESSION_PTRS[2]);
         when(mNativeWrapperMock.halCreateHintSessionWithConfig(anyInt(), anyInt(),
-            any(int[].class), anyLong(), anyInt(),
-            any(SessionConfig.class))).thenThrow(new UnsupportedOperationException());
+                any(int[].class), anyLong(), anyInt(),
+                any(SessionConfig.class))).thenThrow(new UnsupportedOperationException());
     }
 
     static class NativeWrapperFake extends NativeWrapper {
@@ -337,7 +342,7 @@
                 SESSION_TIDS_C, 0L, SessionTag.OTHER, new SessionConfig());
         assertNotNull(c);
         verify(mNativeWrapperMock, times(3)).halCreateHintSession(anyInt(), anyInt(),
-                                                                  any(int[].class), anyLong());
+                any(int[].class), anyLong());
     }
 
     @Test
@@ -487,7 +492,7 @@
 
         AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
                 .createHintSessionWithConfig(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION,
-                    SessionTag.OTHER, new SessionConfig());
+                        SessionTag.OTHER, new SessionConfig());
 
         a.sendHint(PerformanceHintManager.Session.CPU_LOAD_RESET);
         verify(mNativeWrapperMock, times(1)).halSendHint(anyLong(),
@@ -514,7 +519,7 @@
 
         AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
                 .createHintSessionWithConfig(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION,
-                    SessionTag.OTHER, new SessionConfig());
+                        SessionTag.OTHER, new SessionConfig());
 
         service.mUidObserver.onUidStateChanged(
                 a.mUid, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0);
@@ -1096,4 +1101,157 @@
         verify(mIPowerMock, times(1)).getSessionChannel(eq(TGID), eq(UID));
         assertTrue(service.hasChannel(TGID, UID));
     }
+
+    @Test
+    public void testHeadroomPowerHalNotSupported() throws Exception {
+        when(mIPowerMock.getInterfaceVersion()).thenReturn(5);
+        HintManagerService service = createService();
+        assertThrows(UnsupportedOperationException.class, () -> {
+            service.getBinderServiceInstance().getCpuHeadroom(null);
+        });
+        assertThrows(UnsupportedOperationException.class, () -> {
+            service.getBinderServiceInstance().getGpuHeadroom(null);
+        });
+        assertThrows(UnsupportedOperationException.class, () -> {
+            service.getBinderServiceInstance().getCpuHeadroomMinIntervalMillis();
+        });
+        assertThrows(UnsupportedOperationException.class, () -> {
+            service.getBinderServiceInstance().getGpuHeadroomMinIntervalMillis();
+        });
+    }
+
+    @Test
+    public void testCpuHeadroomCache() throws Exception {
+        when(mIPowerMock.getCpuHeadroomMinIntervalMillis()).thenReturn(2000L);
+        CpuHeadroomParamsInternal params1 = new CpuHeadroomParamsInternal();
+        CpuHeadroomParams halParams1 = new CpuHeadroomParams();
+        halParams1.calculationType = CpuHeadroomParams.CalculationType.MIN;
+        halParams1.selectionType = CpuHeadroomParams.SelectionType.ALL;
+        halParams1.pid = Process.myPid();
+
+        CpuHeadroomParamsInternal params2 = new CpuHeadroomParamsInternal();
+        params2.usesDeviceHeadroom = true;
+        params2.calculationType = CpuHeadroomParams.CalculationType.AVERAGE;
+        params2.selectionType = CpuHeadroomParams.SelectionType.PER_CORE;
+        CpuHeadroomParams halParams2 = new CpuHeadroomParams();
+        halParams2.calculationType = CpuHeadroomParams.CalculationType.AVERAGE;
+        halParams2.selectionType = CpuHeadroomParams.SelectionType.PER_CORE;
+        halParams2.pid = DEFAULT_HEADROOM_PID;
+
+        float[] headroom1 = new float[] {0.1f};
+        when(mIPowerMock.getCpuHeadroom(eq(halParams1))).thenReturn(headroom1);
+        float[] headroom2 = new float[] {0.1f, 0.5f};
+        when(mIPowerMock.getCpuHeadroom(eq(halParams2))).thenReturn(headroom2);
+
+        HintManagerService service = createService();
+        clearInvocations(mIPowerMock);
+
+        service.getBinderServiceInstance().getCpuHeadroomMinIntervalMillis();
+        verify(mIPowerMock, times(0)).getCpuHeadroomMinIntervalMillis();
+        service.getBinderServiceInstance().getCpuHeadroom(params1);
+        verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams1));
+        service.getBinderServiceInstance().getCpuHeadroom(params2);
+        verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams2));
+
+        // verify cache is working
+        clearInvocations(mIPowerMock);
+        assertArrayEquals(headroom1, service.getBinderServiceInstance().getCpuHeadroom(params1),
+                0.01f);
+        assertArrayEquals(headroom2, service.getBinderServiceInstance().getCpuHeadroom(params2),
+                0.01f);
+        verify(mIPowerMock, times(0)).getCpuHeadroom(any());
+
+        // after 1 more second it should be served with cache still
+        Thread.sleep(1000);
+        clearInvocations(mIPowerMock);
+        assertArrayEquals(headroom1, service.getBinderServiceInstance().getCpuHeadroom(params1),
+                0.01f);
+        assertArrayEquals(headroom2, service.getBinderServiceInstance().getCpuHeadroom(params2),
+                0.01f);
+        verify(mIPowerMock, times(0)).getCpuHeadroom(any());
+
+        // after 1.5 more second it should be served with cache still as timer reset
+        Thread.sleep(1500);
+        clearInvocations(mIPowerMock);
+        assertArrayEquals(headroom1, service.getBinderServiceInstance().getCpuHeadroom(params1),
+                0.01f);
+        assertArrayEquals(headroom2, service.getBinderServiceInstance().getCpuHeadroom(params2),
+                0.01f);
+        verify(mIPowerMock, times(0)).getCpuHeadroom(any());
+
+        // after 2+ seconds it should be served from HAL as it exceeds 2000 millis interval
+        Thread.sleep(2100);
+        clearInvocations(mIPowerMock);
+        assertArrayEquals(headroom1, service.getBinderServiceInstance().getCpuHeadroom(params1),
+                0.01f);
+        assertArrayEquals(headroom2, service.getBinderServiceInstance().getCpuHeadroom(params2),
+                0.01f);
+        verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams1));
+        verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams2));
+    }
+
+    @Test
+    public void testGpuHeadroomCache() throws Exception {
+        when(mIPowerMock.getGpuHeadroomMinIntervalMillis()).thenReturn(2000L);
+        GpuHeadroomParamsInternal params1 = new GpuHeadroomParamsInternal();
+        GpuHeadroomParams halParams1 = new GpuHeadroomParams();
+        halParams1.calculationType = GpuHeadroomParams.CalculationType.MIN;
+
+        GpuHeadroomParamsInternal params2 = new GpuHeadroomParamsInternal();
+        GpuHeadroomParams halParams2 = new GpuHeadroomParams();
+        params2.calculationType =
+                halParams2.calculationType = GpuHeadroomParams.CalculationType.AVERAGE;
+
+        float headroom1 = 0.1f;
+        when(mIPowerMock.getGpuHeadroom(eq(halParams1))).thenReturn(headroom1);
+        float headroom2 = 0.2f;
+        when(mIPowerMock.getGpuHeadroom(eq(halParams2))).thenReturn(headroom2);
+        HintManagerService service = createService();
+        clearInvocations(mIPowerMock);
+
+        service.getBinderServiceInstance().getGpuHeadroomMinIntervalMillis();
+        verify(mIPowerMock, times(0)).getGpuHeadroomMinIntervalMillis();
+        assertEquals(headroom1, service.getBinderServiceInstance().getGpuHeadroom(params1),
+                0.01f);
+        assertEquals(headroom2, service.getBinderServiceInstance().getGpuHeadroom(params2),
+                0.01f);
+        verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams1));
+        verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams2));
+
+        // verify cache is working
+        clearInvocations(mIPowerMock);
+        assertEquals(headroom1, service.getBinderServiceInstance().getGpuHeadroom(params1),
+                0.01f);
+        assertEquals(headroom2, service.getBinderServiceInstance().getGpuHeadroom(params2),
+                0.01f);
+        verify(mIPowerMock, times(0)).getGpuHeadroom(any());
+
+        // after 1 more second it should be served with cache still
+        Thread.sleep(1000);
+        clearInvocations(mIPowerMock);
+        assertEquals(headroom1, service.getBinderServiceInstance().getGpuHeadroom(params1),
+                0.01f);
+        assertEquals(headroom2, service.getBinderServiceInstance().getGpuHeadroom(params2),
+                0.01f);
+        verify(mIPowerMock, times(0)).getGpuHeadroom(any());
+
+        // after 1.5 more second it should be served with cache still as timer reset
+        Thread.sleep(1500);
+        clearInvocations(mIPowerMock);
+        assertEquals(headroom1, service.getBinderServiceInstance().getGpuHeadroom(params1),
+                0.01f);
+        assertEquals(headroom2, service.getBinderServiceInstance().getGpuHeadroom(params2),
+                0.01f);
+        verify(mIPowerMock, times(0)).getGpuHeadroom(any());
+
+        // after 2+ seconds it should be served from HAL as it exceeds 2000 millis interval
+        Thread.sleep(2100);
+        clearInvocations(mIPowerMock);
+        assertEquals(headroom1, service.getBinderServiceInstance().getGpuHeadroom(params1),
+                0.01f);
+        assertEquals(headroom2, service.getBinderServiceInstance().getGpuHeadroom(params2),
+                0.01f);
+        verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams1));
+        verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams2));
+    }
 }
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index f165667..0c058df 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -97,7 +97,7 @@
         "com_android_server_accessibility_flags_lib",
         "locksettings_flags_lib",
     ] + select(soong_config_variable("ANDROID", "release_crashrecovery_module"), {
-        "true": ["service-crashrecovery.impl"],
+        "true": ["service-crashrecovery-pre-jarjar"],
         default: [],
     }),
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
index cc02865..6af6542 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
@@ -84,7 +84,9 @@
 import com.android.internal.R;
 import com.android.server.UiServiceTestCase;
 import com.android.server.notification.GroupHelper.CachedSummary;
+import com.android.server.notification.GroupHelper.FullyQualifiedGroupKey;
 import com.android.server.notification.GroupHelper.NotificationAttributes;
+import com.android.server.notification.GroupHelper.NotificationSectioner;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -2298,6 +2300,7 @@
         final String pkg = "package";
         final String expectedGroupKey_alerting = GroupHelper.getFullAggregateGroupKey(pkg,
                 AGGREGATE_GROUP_KEY + "AlertingSection", UserHandle.SYSTEM.getIdentifier());
+        final int numNotifications = 2 * AUTOGROUP_AT_COUNT;
         int numNotificationChannel1 = 0;
         final NotificationChannel channel1 = new NotificationChannel("TEST_CHANNEL_ID1",
                 "TEST_CHANNEL_ID1", IMPORTANCE_DEFAULT);
@@ -2307,7 +2310,7 @@
         final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
         // Post notifications with different channels that autogroup within the same section
         NotificationRecord r;
-        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+        for (int i = 0; i < numNotifications; i++) {
             if (i % 2 == 0) {
                 r = getNotificationRecord(pkg, i, String.valueOf(i),
                         UserHandle.SYSTEM, "testGrp " + i, false, channel1);
@@ -2324,12 +2327,12 @@
                 "TEST_CHANNEL_ID1");
         verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
                 eq(expectedGroupKey_alerting), anyInt(), eq(expectedSummaryAttr));
-        verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(),
+        verify(mCallback, times(numNotifications)).addAutoGroup(anyString(),
                 eq(expectedGroupKey_alerting), eq(true));
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
-        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
-                any());
+        verify(mCallback, times(numNotifications - AUTOGROUP_AT_COUNT)).updateAutogroupSummary(
+                anyInt(), anyString(), anyString(), any());
         Mockito.reset(mCallback);
 
         // Update channel1's importance
@@ -2375,7 +2378,7 @@
         final List<NotificationRecord> notificationList = new ArrayList<>();
         final String pkg = "package";
         final int summaryId = 0;
-        final int numChildNotif = 4;
+        final int numChildNotif = 2 * AUTOGROUP_AT_COUNT;
 
         // Create an app-provided group: summary + child notifications
         final NotificationChannel channel1 = new NotificationChannel("TEST_CHANNEL_ID1",
@@ -2435,8 +2438,8 @@
                 eq(expectedGroupKey_social), eq(true));
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
-        verify(mCallback, times(numChildNotif / 2)).updateAutogroupSummary(anyInt(), anyString(),
-                anyString(), any());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+                any());
         verify(mCallback, times(numChildNotif)).removeAppProvidedSummaryOnClassification(
                 anyString(), eq(originalAppGroupKey));
     }
@@ -2513,9 +2516,10 @@
         final List<NotificationRecord> notificationList = new ArrayList<>();
         final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
         final String pkg = "package";
+        final int numChildNotifications = AUTOGROUP_AT_COUNT;
 
         // Post singleton groups, above forced group limit
-        for (int i = 0; i < AUTOGROUP_SINGLETONS_AT_COUNT; i++) {
+        for (int i = 0; i < numChildNotifications; i++) {
             NotificationRecord summary = getNotificationRecord(pkg, i,
                     String.valueOf(i), UserHandle.SYSTEM, "testGrp " + i, true);
             notificationList.add(summary);
@@ -2545,13 +2549,13 @@
         // Check that notifications are forced grouped and app-provided summaries are canceled
         verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
                 eq(expectedGroupKey_social), anyInt(), eq(expectedSummaryAttr_social));
-        verify(mCallback, times(AUTOGROUP_SINGLETONS_AT_COUNT)).addAutoGroup(anyString(),
+        verify(mCallback, times(numChildNotifications)).addAutoGroup(anyString(),
                 eq(expectedGroupKey_social), eq(true));
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
-        verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
                 any());
-        verify(mCallback, times(2)).removeAppProvidedSummaryOnClassification(
+        verify(mCallback, times(numChildNotifications)).removeAppProvidedSummaryOnClassification(
                 anyString(), anyString());
 
         // Adjust group key and cancel summaries
@@ -2593,14 +2597,16 @@
                 AGGREGATE_GROUP_KEY + "AlertingSection", UserHandle.SYSTEM.getIdentifier());
         String expectedTriggeringKey = null;
         // Post singleton groups, above forced group limit
-        for (int i = 0; i < AUTOGROUP_SINGLETONS_AT_COUNT; i++) {
+        for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
             NotificationRecord summary = getNotificationRecord(pkg, i,
                     String.valueOf(i), UserHandle.SYSTEM, "testGrp " + i, true);
             notificationList.add(summary);
             NotificationRecord child = getNotificationRecord(pkg, i + 42,
                     String.valueOf(i + 42), UserHandle.SYSTEM, "testGrp " + i, false);
             notificationList.add(child);
-            expectedTriggeringKey = child.getKey();
+            if (i == AUTOGROUP_SINGLETONS_AT_COUNT - 1) {
+                expectedTriggeringKey = child.getKey();
+            }
             summaryByGroup.put(summary.getGroupKey(), summary);
             mGroupHelper.onNotificationPostedWithDelay(child, notificationList, summaryByGroup);
             summary.isCanceled = true;  // simulate removing the app summary
@@ -2611,14 +2617,8 @@
         verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg),
                 eq(expectedTriggeringKey), eq(expectedGroupKey_alerting), anyInt(),
                 eq(getNotificationAttributes(BASE_FLAGS)));
-        verify(mCallback, times(AUTOGROUP_SINGLETONS_AT_COUNT)).addAutoGroup(anyString(),
-                eq(expectedGroupKey_alerting), eq(true));
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
-        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
-                any());
-        verify(mCallback, times(AUTOGROUP_SINGLETONS_AT_COUNT)).removeAppProvidedSummary(
-                anyString());
         assertThat(mGroupHelper.findCanceledSummary(pkg, String.valueOf(0), 0,
                 UserHandle.SYSTEM.getIdentifier())).isNotNull();
         assertThat(mGroupHelper.findCanceledSummary(pkg, String.valueOf(1), 1,
@@ -2645,12 +2645,12 @@
         // Check that all notifications are moved to the social section group
         verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
                 eq(expectedGroupKey_social), anyInt(), eq(expectedSummaryAttr_social));
-        verify(mCallback, times(AUTOGROUP_SINGLETONS_AT_COUNT)).addAutoGroup(anyString(),
+        verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(),
                 eq(expectedGroupKey_social), eq(true));
         // Check that the alerting section group is removed
         verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), eq(pkg),
                 eq(expectedGroupKey_alerting));
-        verify(mCallback, times(AUTOGROUP_SINGLETONS_AT_COUNT)).updateAutogroupSummary(anyInt(),
+        verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).updateAutogroupSummary(anyInt(),
                 anyString(), anyString(), any());
     }
 
@@ -2666,7 +2666,7 @@
         final String pkg = "package";
 
         final int summaryId = 0;
-        final int numChildren = 3;
+        final int numChildren = AUTOGROUP_AT_COUNT;
         // Post a regular/valid group: summary + notifications
         NotificationRecord summary = getNotificationRecord(pkg, summaryId,
                 String.valueOf(summaryId), UserHandle.SYSTEM, "testGrp", true);
@@ -2706,13 +2706,211 @@
                 eq(true));
         verify(mCallback, never()).removeAutoGroup(anyString());
         verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
-        verify(mCallback, times(numChildren - 1)).updateAutogroupSummary(anyInt(), anyString(),
-                anyString(), any());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+                any());
         verify(mCallback, times(numChildren)).removeAppProvidedSummaryOnClassification(anyString(),
                 anyString());
     }
 
     @Test
+    @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+            FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION,
+            FLAG_NOTIFICATION_CLASSIFICATION})
+    public void testUnbundleNotification_originalSummaryMissing_autogroupInNewSection() {
+        // Check that unbundled notifications are moved to the original section and aggregated
+        // with existing autogrouped notifications
+        final List<NotificationRecord> notificationList = new ArrayList<>();
+        final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+        final String pkg = "package";
+
+        final int summaryId = 0;
+        final int numChildren = AUTOGROUP_AT_COUNT - 1;
+        // Post a regular/valid group: summary + notifications (one less than autogroup limit)
+        NotificationRecord summary = getNotificationRecord(pkg, summaryId,
+                String.valueOf(summaryId), UserHandle.SYSTEM, "testGrp", true);
+        notificationList.add(summary);
+        summaryByGroup.put(summary.getGroupKey(), summary);
+        final String originalAppGroupKey = summary.getGroupKey();
+        final NotificationChannel originalChannel = summary.getChannel();
+        for (int i = 0; i < numChildren; i++) {
+            NotificationRecord child = getNotificationRecord(pkg, i + 42, String.valueOf(i + 42),
+                    UserHandle.SYSTEM, "testGrp", false);
+            notificationList.add(child);
+            mGroupHelper.onNotificationPostedWithDelay(child, notificationList, summaryByGroup);
+        }
+
+        // Classify/bundle all child notifications: original group & summary is removed
+        final NotificationChannel socialChannel = new NotificationChannel(
+                NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID,
+                IMPORTANCE_DEFAULT);
+        for (NotificationRecord record: notificationList) {
+            if (record.getOriginalGroupKey().contains("testGrp")
+                    && record.getNotification().isGroupChild()) {
+                record.updateNotificationChannel(socialChannel);
+                mGroupHelper.onChannelUpdated(record);
+            }
+        }
+
+        // Check that no autogroup summaries were created for the social section
+        verify(mCallback, never()).addAutoGroupSummary(anyInt(), anyString(), anyString(),
+                anyString(), anyInt(), any());
+        verify(mCallback, never()).addAutoGroup(anyString(), anyString(), anyBoolean());
+        verify(mCallback, never()).removeAutoGroup(anyString());
+        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+                any());
+        verify(mCallback, times(numChildren)).removeAppProvidedSummaryOnClassification(
+                anyString(), eq(originalAppGroupKey));
+
+        // Cancel summary
+        summary.isCanceled = true;
+        summaryByGroup.clear();
+        notificationList.remove(summary);
+
+        // Add 1 ungrouped notification in the original section
+        NotificationRecord ungroupedNotification = getNotificationRecord(pkg, 4242,
+                String.valueOf(4242), UserHandle.SYSTEM);
+        notificationList.add(ungroupedNotification);
+        mGroupHelper.onNotificationPosted(ungroupedNotification, false);
+
+        // Unbundle the bundled notifications => notifications are moved back to the original group
+        // and an aggregate group is created because autogroup limit is reached
+        reset(mCallback);
+        for (NotificationRecord record: notificationList) {
+            if (record.getNotification().isGroupChild()
+                    && record.getOriginalGroupKey().contains("testGrp")
+                    && NotificationChannel.SYSTEM_RESERVED_IDS.contains(
+                        record.getChannel().getId())) {
+                record.updateNotificationChannel(originalChannel);
+                mGroupHelper.onNotificationUnbundled(record, false);
+            }
+        }
+
+        // Check that a new aggregate group is created
+        final String expectedGroupKey_alerting = GroupHelper.getFullAggregateGroupKey(pkg,
+                AGGREGATE_GROUP_KEY + "AlertingSection", UserHandle.SYSTEM.getIdentifier());
+        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+                eq(expectedGroupKey_alerting), anyInt(), any());
+        verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(),
+                eq(expectedGroupKey_alerting), eq(true));
+        verify(mCallback, never()).removeAutoGroup(anyString());
+        verify(mCallback, times(numChildren)).removeAutoGroupSummary(anyInt(), anyString(),
+                anyString());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+                any());
+    }
+
+    @Test
+    @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+            FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION,
+            FLAG_NOTIFICATION_CLASSIFICATION})
+    public void testUnbundleNotification_originalSummaryExists() {
+        // Check that unbundled notifications are moved to the original section and original group
+        // when the original summary is still present
+        final List<NotificationRecord> notificationList = new ArrayList<>();
+        final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+        final String pkg = "package";
+
+        final int summaryId = 0;
+        final int numChildren = AUTOGROUP_AT_COUNT + 1;
+        // Post a regular/valid group: summary + notifications
+        NotificationRecord summary = getNotificationRecord(pkg, summaryId,
+                String.valueOf(summaryId), UserHandle.SYSTEM, "testGrp", true);
+        notificationList.add(summary);
+        summaryByGroup.put(summary.getGroupKey(), summary);
+        final String originalAppGroupKey = summary.getGroupKey();
+        final NotificationChannel originalChannel = summary.getChannel();
+        for (int i = 0; i < numChildren; i++) {
+            NotificationRecord child = getNotificationRecord(pkg, i + 42, String.valueOf(i + 42),
+                    UserHandle.SYSTEM, "testGrp", false);
+            notificationList.add(child);
+            mGroupHelper.onNotificationPostedWithDelay(child, notificationList, summaryByGroup);
+        }
+
+        // Classify/bundle child notifications: all except one, to keep the original group
+        final NotificationChannel socialChannel = new NotificationChannel(
+                NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID,
+                IMPORTANCE_DEFAULT);
+        final String expectedGroupKey_social = GroupHelper.getFullAggregateGroupKey(pkg,
+                AGGREGATE_GROUP_KEY + "SocialSection", UserHandle.SYSTEM.getIdentifier());
+        final NotificationAttributes expectedSummaryAttr_social = new NotificationAttributes(
+                BASE_FLAGS, mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
+                NotificationChannel.SOCIAL_MEDIA_ID);
+        int numChildrenBundled = 0;
+        for (NotificationRecord record: notificationList) {
+            if (record.getOriginalGroupKey().contains("testGrp")
+                    && record.getNotification().isGroupChild()) {
+                record.updateNotificationChannel(socialChannel);
+                mGroupHelper.onChannelUpdated(record);
+                numChildrenBundled++;
+                if (numChildrenBundled == AUTOGROUP_AT_COUNT) {
+                    break;
+                }
+            }
+        }
+
+        // Check that 1 autogroup summaries were created for the social section
+        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+                eq(expectedGroupKey_social), anyInt(), eq(expectedSummaryAttr_social));
+        verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(),
+                eq(expectedGroupKey_social), eq(true));
+        verify(mCallback, never()).removeAutoGroup(anyString());
+        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+        verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+                any());
+        verify(mCallback, times(AUTOGROUP_AT_COUNT)).removeAppProvidedSummaryOnClassification(
+                anyString(), eq(originalAppGroupKey));
+
+        // Adjust group key and cancel summaries
+        for (NotificationRecord record: notificationList) {
+            if (record.getNotification().isGroupSummary()) {
+                record.isCanceled = true;
+            } else if (record.getOriginalGroupKey().contains("testGrp")
+                        && NotificationChannel.SYSTEM_RESERVED_IDS.contains(
+                        record.getChannel().getId())) {
+                record.setOverrideGroupKey(expectedGroupKey_social);
+            }
+        }
+
+        // Add 1 ungrouped notification in the original section
+        NotificationRecord ungroupedNotification = getNotificationRecord(pkg, 4242,
+                String.valueOf(4242), UserHandle.SYSTEM);
+        notificationList.add(ungroupedNotification);
+        mGroupHelper.onNotificationPosted(ungroupedNotification, false);
+
+        // Unbundle the bundled notifications => social section summary is destroyed
+        // and notifications are moved back to the original group
+        reset(mCallback);
+        for (NotificationRecord record: notificationList) {
+            if (record.getNotification().isGroupChild()
+                    && record.getOriginalGroupKey().contains("testGrp")
+                    && NotificationChannel.SYSTEM_RESERVED_IDS.contains(
+                        record.getChannel().getId())) {
+                record.updateNotificationChannel(originalChannel);
+                mGroupHelper.onNotificationUnbundled(record, true);
+            }
+        }
+
+        // Check that the autogroup summary for the social section was removed
+        // and that no new autogroup summaries were created
+        verify(mCallback, never()).addAutoGroupSummary(anyInt(), anyString(), anyString(),
+                anyString(), anyInt(), any());
+        verify(mCallback, never()).addAutoGroup(anyString(), anyString(), anyBoolean());
+        verify(mCallback, never()).removeAutoGroup(anyString());
+        verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), eq(pkg),
+                eq(expectedGroupKey_social));
+        verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).updateAutogroupSummary(anyInt(), eq(pkg),
+                eq(expectedGroupKey_social), any());
+
+        for (NotificationRecord record: notificationList) {
+            if (record.getNotification().isGroupChild()
+                    && record.getOriginalGroupKey().contains("testGrp")) {
+                assertThat(record.getSbn().getOverrideGroupKey()).isNull();
+            }
+        }
+    }
+
+    @Test
     @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
     public void testMoveAggregateGroups_updateChannel_groupsUngrouped() {
         final String pkg = "package";
@@ -3059,6 +3257,120 @@
     @Test
     @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
     @DisableFlags(FLAG_NOTIFICATION_FORCE_GROUP_CONVERSATIONS)
+    public void testNonGroupableChildren_singletonGroups_disableConversations() {
+        // Check that singleton groups with children that are not groupable, is not grouped
+        // Even though the group summary is a regular (alerting) notification, the children are
+        // conversations => the group should not be forced grouped.
+        final List<NotificationRecord> notificationList = new ArrayList<>();
+        final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+        final String pkg = "package";
+
+        // Trigger notification, ungrouped
+        final int triggerId = 1;
+        NotificationRecord triggerNotification = getNotificationRecord(pkg, triggerId,
+                String.valueOf(triggerId), UserHandle.SYSTEM);
+        notificationList.add(triggerNotification);
+        final NotificationSectioner triggerSection = GroupHelper.getSection(triggerNotification);
+        final FullyQualifiedGroupKey triggerFullAggregateGroupKey = new FullyQualifiedGroupKey(
+                triggerNotification.getUserId(), triggerNotification.getSbn().getPackageName(),
+                triggerSection);
+
+        // Add singleton group with alerting child
+        final String groupName_valid = "testGrp_valid";
+        final int summaryId_valid = 0;
+        NotificationRecord summary = getNotificationRecord(pkg, summaryId_valid,
+                String.valueOf(summaryId_valid), UserHandle.SYSTEM, groupName_valid, true);
+        notificationList.add(summary);
+        summaryByGroup.put(summary.getGroupKey(), summary);
+        final String groupKey_valid = summary.getGroupKey();
+        NotificationRecord child = getNotificationRecord(pkg, summaryId_valid + 42,
+                String.valueOf(summaryId_valid + 42), UserHandle.SYSTEM, groupName_valid, false);
+        notificationList.add(child);
+
+        // Add singleton group with conversation child
+        final String groupName_invalid = "testGrp_invalid";
+        final int summaryId_invalid = 100;
+        summary = getNotificationRecord(pkg, summaryId_invalid,
+                String.valueOf(summaryId_invalid), UserHandle.SYSTEM, groupName_invalid, true);
+        notificationList.add(summary);
+        final String groupKey_invalid = summary.getGroupKey();
+        summaryByGroup.put(summary.getGroupKey(), summary);
+        child = getNotificationRecord(pkg, summaryId_invalid + 42,
+                String.valueOf(summaryId_invalid + 42), UserHandle.SYSTEM, groupName_invalid,
+                false);
+        child = spy(child);
+        when(child.isConversation()).thenReturn(true);
+        notificationList.add(child);
+
+        // Check that the invalid group will not be force grouped
+        final ArrayMap<String, NotificationRecord> sparseGroups = mGroupHelper.getSparseGroups(
+                triggerFullAggregateGroupKey, notificationList, summaryByGroup, triggerSection);
+        assertThat(sparseGroups).containsKey(groupKey_valid);
+        assertThat(sparseGroups).doesNotContainKey(groupKey_invalid);
+    }
+
+    @Test
+    @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_FORCE_GROUP_CONVERSATIONS})
+    public void testNonGroupableChildren_singletonGroups_enableConversations() {
+        // Check that singleton groups with children that are not groupable, is not grouped
+        // Conversations are groupable (FLAG_NOTIFICATION_FORCE_GROUP_CONVERSATIONS is enabled)
+        // The invalid group is the alerting notifications: because the triggering notifications'
+        // section is Conversations, so the alerting group should be skipped.
+        final List<NotificationRecord> notificationList = new ArrayList<>();
+        final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+        final String pkg = "package";
+
+        // Trigger notification, ungrouped conversation
+        final int triggerId = 1;
+        NotificationRecord triggerNotification = getNotificationRecord(pkg, triggerId,
+                String.valueOf(triggerId), UserHandle.SYSTEM);
+        triggerNotification = spy(triggerNotification);
+        when(triggerNotification.isConversation()).thenReturn(true);
+        notificationList.add(triggerNotification);
+        final NotificationSectioner triggerSection = GroupHelper.getSection(triggerNotification);
+        final FullyQualifiedGroupKey triggerFullAggregateGroupKey = new FullyQualifiedGroupKey(
+                triggerNotification.getUserId(), triggerNotification.getSbn().getPackageName(),
+                triggerSection);
+
+        // Add singleton group with conversation child
+        final String groupName_valid = "testGrp_valid";
+        final int summaryId_valid = 0;
+        NotificationRecord summary = getNotificationRecord(pkg, summaryId_valid,
+                String.valueOf(summaryId_valid), UserHandle.SYSTEM, groupName_valid, true);
+        summary = spy(summary);
+        when(summary.isConversation()).thenReturn(true);
+        notificationList.add(summary);
+        summaryByGroup.put(summary.getGroupKey(), summary);
+        final String groupKey_valid = summary.getGroupKey();
+        NotificationRecord child = getNotificationRecord(pkg, summaryId_valid + 42,
+                String.valueOf(summaryId_valid + 42), UserHandle.SYSTEM, groupName_valid, false);
+        child = spy(child);
+        when(child.isConversation()).thenReturn(true);
+        notificationList.add(child);
+
+        // Add singleton group with non-conversation child
+        final String groupName_invalid = "testGrp_invalid";
+        final int summaryId_invalid = 100;
+        summary = getNotificationRecord(pkg, summaryId_invalid,
+                String.valueOf(summaryId_invalid), UserHandle.SYSTEM, groupName_invalid, true);
+        notificationList.add(summary);
+        final String groupKey_invalid = summary.getGroupKey();
+        summaryByGroup.put(summary.getGroupKey(), summary);
+        child = getNotificationRecord(pkg, summaryId_invalid + 42,
+                String.valueOf(summaryId_invalid + 42), UserHandle.SYSTEM, groupName_invalid,
+                false);
+        notificationList.add(child);
+
+        // Check that the invalid group will not be force grouped
+        final ArrayMap<String, NotificationRecord> sparseGroups = mGroupHelper.getSparseGroups(
+                triggerFullAggregateGroupKey, notificationList, summaryByGroup, triggerSection);
+        assertThat(sparseGroups).containsKey(groupKey_valid);
+        assertThat(sparseGroups).doesNotContainKey(groupKey_invalid);
+    }
+
+    @Test
+    @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+    @DisableFlags(FLAG_NOTIFICATION_FORCE_GROUP_CONVERSATIONS)
     public void testNonGroupableNotifications() {
         // Check that there is no valid section for: conversations, calls, foreground services
         NotificationRecord notification_conversation = mock(NotificationRecord.class);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
index 6eb2f71..9eddcc9 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
@@ -720,6 +720,7 @@
     }
 
     @Test
+    @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
     public void testDefaultAllowedKeyAdjustments_readWriteXml() throws Exception {
         mAssistants.loadDefaultsFromConfig(true);
 
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index c7f4056..3c2f961 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -1913,6 +1913,55 @@
                 fakeVibrator.getEffectSegments(vibration5.id));
     }
 
+    @Test
+    public void vibrate_multipleVibratorsSequentialInSession_runsInOrderWithoutDelaysAndNoOffs() {
+        mockVibrators(1, 2, 3);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+        mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(2).setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_CLICK);
+        mVibratorProviders.get(3).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+
+        CombinedVibration effect = CombinedVibration.startSequential()
+                .addNext(3,
+                        VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+                        /* delay= */ TEST_TIMEOUT_MILLIS)
+                .addNext(1,
+                        VibrationEffect.createWaveform(
+                                new long[] {TEST_TIMEOUT_MILLIS, TEST_TIMEOUT_MILLIS},
+                                /* repeat= */ -1),
+                        /* delay= */ TEST_TIMEOUT_MILLIS)
+                .addNext(2,
+                        VibrationEffect.startComposition()
+                                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1,
+                                        /* delay= */ TEST_TIMEOUT_MILLIS)
+                                .compose(),
+                        /* delay= */ TEST_TIMEOUT_MILLIS)
+                .combine();
+        HalVibration vibration = startThreadAndDispatcher(effect, /* isInSession= */ true);
+
+        // Should not timeout as delays will not affect in session playback time.
+        waitForCompletion();
+
+        // Vibrating state remains ON until session resets it.
+        verifyCallbacksTriggered(vibration, Status.FINISHED);
+        assertTrue(mControllers.get(1).isVibrating());
+        assertTrue(mControllers.get(2).isVibrating());
+        assertTrue(mControllers.get(3).isVibrating());
+
+        assertEquals(0, mVibratorProviders.get(1).getOffCount());
+        assertEquals(0, mVibratorProviders.get(2).getOffCount());
+        assertEquals(0, mVibratorProviders.get(3).getOffCount());
+        assertEquals(Arrays.asList(expectedOneShot(TEST_TIMEOUT_MILLIS)),
+                mVibratorProviders.get(1).getEffectSegments(vibration.id));
+        assertEquals(expectedAmplitudes(255), mVibratorProviders.get(1).getAmplitudes());
+        assertEquals(Arrays.asList(expectedPrimitive(
+                VibrationEffect.Composition.PRIMITIVE_CLICK, 1, TEST_TIMEOUT_MILLIS)),
+                mVibratorProviders.get(2).getEffectSegments(vibration.id));
+        assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)),
+                mVibratorProviders.get(3).getEffectSegments(vibration.id));
+    }
+
     private void mockVibrators(int... vibratorIds) {
         for (int vibratorId : vibratorIds) {
             mVibratorProviders.put(vibratorId,
@@ -1935,8 +1984,14 @@
         return startThreadAndDispatcher(createVibration(effect));
     }
 
+    private HalVibration startThreadAndDispatcher(CombinedVibration effect, boolean isInSession) {
+        return startThreadAndDispatcher(createVibration(effect), isInSession,
+                /* requestVibrationParamsFuture= */ null);
+    }
+
     private HalVibration startThreadAndDispatcher(HalVibration vib) {
-        return startThreadAndDispatcher(vib, /* requestVibrationParamsFuture= */ null);
+        return startThreadAndDispatcher(vib, /* isInSession= */ false,
+                /* requestVibrationParamsFuture= */ null);
     }
 
     private HalVibration startThreadAndDispatcher(VibrationEffect effect,
@@ -1947,15 +2002,17 @@
         HalVibration vib = new HalVibration(
                 new CallerInfo(attrs, UID, DEVICE_ID, PACKAGE_NAME, "reason"),
                 CombinedVibration.createParallel(effect));
-        return startThreadAndDispatcher(vib, requestVibrationParamsFuture);
+        return startThreadAndDispatcher(vib, /* isInSession= */ false,
+                requestVibrationParamsFuture);
     }
 
-    private HalVibration startThreadAndDispatcher(HalVibration vib,
+    private HalVibration startThreadAndDispatcher(HalVibration vib, boolean isInSession,
             CompletableFuture<Void> requestVibrationParamsFuture) {
         mControllers = createVibratorControllers();
         DeviceAdapter deviceAdapter = new DeviceAdapter(mVibrationSettings, mControllers);
-        mVibrationConductor = new VibrationStepConductor(vib, mVibrationSettings, deviceAdapter,
-                mVibrationScaler, mStatsLoggerMock, requestVibrationParamsFuture, mManagerHooks);
+        mVibrationConductor = new VibrationStepConductor(vib, isInSession, mVibrationSettings,
+                deviceAdapter, mVibrationScaler, mStatsLoggerMock, requestVibrationParamsFuture,
+                mManagerHooks);
         assertTrue(mThread.runVibrationOnVibrationThread(mVibrationConductor));
         return mVibrationConductor.getVibration();
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 42e31de..69fe7c9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -82,6 +82,7 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
 import android.util.ArrayMap;
@@ -1299,6 +1300,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_PIP2)
     public void testEnterPipParams() {
         final StubOrganizer o = new StubOrganizer();
         mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(o);
@@ -1314,6 +1316,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_PIP2)
     public void testChangePipParams() {
         class ChangeSavingOrganizer extends StubOrganizer {
             RunningTaskInfo mChangedInfo;
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 1294945..15c8b13 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -185,6 +185,7 @@
     private static final int MSG_INCREASE_SENDSTRING_COUNT = 21;
     private static final int MSG_UPDATE_USB_SPEED = 22;
     private static final int MSG_UPDATE_HAL_VERSION = 23;
+    private static final int MSG_USER_UNLOCKED_AFTER_BOOT = 24;
 
     // Delay for debouncing USB disconnects.
     // We often get rapid connect/disconnect events when enabling USB functions,
@@ -414,6 +415,17 @@
             }
         };
 
+        if (Flags.checkUserActionUnlocked()) {
+            BroadcastReceiver userUnlockedAfterBootReceiver = new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    mHandler.sendEmptyMessage(MSG_USER_UNLOCKED_AFTER_BOOT);
+                }
+            };
+            mContext.registerReceiver(userUnlockedAfterBootReceiver,
+                    new IntentFilter(Intent.ACTION_USER_UNLOCKED));
+        }
+
         mContext.registerReceiver(portReceiver,
                 new IntentFilter(UsbManager.ACTION_USB_PORT_CHANGED));
         mContext.registerReceiver(chargingReceiver,
@@ -474,6 +486,7 @@
         mHandler.sendEmptyMessage(MSG_SYSTEM_READY);
     }
 
+    // Same as ACTION_LOCKED_BOOT_COMPLETED.
     public void bootCompleted() {
         if (DEBUG) Slog.d(TAG, "boot completed");
         mHandler.sendEmptyMessage(MSG_BOOT_COMPLETED);
@@ -632,7 +645,7 @@
         protected int mUsbSpeed;
         protected int mCurrentGadgetHalVersion;
         protected boolean mPendingBootAccessoryHandshakeBroadcast;
-
+        protected boolean mUserUnlockedAfterBoot;
         /**
          * The persistent property which stores whether adb is enabled or not.
          * May also contain vendor-specific default functions for testing purposes.
@@ -837,6 +850,12 @@
             return !userManager.hasUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER);
         }
 
+        private void attachAccessory() {
+            mUsbDeviceManager.getCurrentSettings().accessoryAttached(mCurrentAccessory);
+            removeMessages(MSG_ACCESSORY_HANDSHAKE_TIMEOUT);
+            broadcastUsbAccessoryHandshake();
+        }
+
         private void updateCurrentAccessory() {
             // We are entering accessory mode if we have received a request from the host
             // and the request has not timed out yet.
@@ -863,10 +882,13 @@
 
                     Slog.d(TAG, "entering USB accessory mode: " + mCurrentAccessory);
                     // defer accessoryAttached if system is not ready
-                    if (mBootCompleted) {
-                        mUsbDeviceManager.getCurrentSettings().accessoryAttached(mCurrentAccessory);
-                        removeMessages(MSG_ACCESSORY_HANDSHAKE_TIMEOUT);
-                        broadcastUsbAccessoryHandshake();
+                    if (!Flags.checkUserActionUnlocked() && mBootCompleted) {
+                        attachAccessory();
+                    }
+                    // Defer accessoryAttached till user unlocks after boot.
+                    // When no pin pattern is set, ACTION_USER_UNLOCKED would fire anyways
+                    if (Flags.checkUserActionUnlocked() && mUserUnlockedAfterBoot) {
+                        attachAccessory();
                     } // else handle in boot completed
                 } else {
                     Slog.e(TAG, "nativeGetAccessoryStrings failed");
@@ -887,7 +909,10 @@
             setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId);
 
             if (mCurrentAccessory != null) {
-                if (mBootCompleted) {
+                if (!Flags.checkUserActionUnlocked() && mBootCompleted) {
+                    mPermissionManager.usbAccessoryRemoved(mCurrentAccessory);
+                }
+                if (Flags.checkUserActionUnlocked() && mUserUnlockedAfterBoot) {
                     mPermissionManager.usbAccessoryRemoved(mCurrentAccessory);
                 }
                 mCurrentAccessory = null;
@@ -1377,6 +1402,7 @@
                 case MSG_BOOT_COMPLETED:
                     operationId = sUsbOperationCount.incrementAndGet();
                     mBootCompleted = true;
+                    if (DEBUG) Slog.d(TAG, "MSG_BOOT_COMPLETED");
                     finishBoot(operationId);
                     break;
                 case MSG_USER_SWITCHED: {
@@ -1423,14 +1449,38 @@
                 }
                 case MSG_INCREASE_SENDSTRING_COUNT: {
                     mSendStringCount = mSendStringCount + 1;
+                    break;
+                }
+                case MSG_USER_UNLOCKED_AFTER_BOOT: {
+                    if (DEBUG) Slog.d(TAG, "MSG_USER_UNLOCKED_AFTER_BOOT");
+                    if (mUserUnlockedAfterBoot) {
+                        break;
+                    }
+                    mUserUnlockedAfterBoot = true;
+                    if (mCurrentUsbFunctionsReceived && mUserUnlockedAfterBoot) {
+                        attachAccessoryAfterBoot();
+                    }
+                    break;
                 }
             }
         }
 
+        private void attachAccessoryAfterBoot() {
+            if (mCurrentAccessory != null) {
+                Slog.i(TAG, "AccessoryAttached");
+                mUsbDeviceManager.getCurrentSettings().accessoryAttached(mCurrentAccessory);
+                broadcastUsbAccessoryHandshake();
+            } else if (mPendingBootAccessoryHandshakeBroadcast) {
+                broadcastUsbAccessoryHandshake();
+            }
+            mPendingBootAccessoryHandshakeBroadcast = false;
+        }
+
         public abstract void handlerInitDone(int operationId);
 
         protected void finishBoot(int operationId) {
             if (mBootCompleted && mCurrentUsbFunctionsReceived && mSystemReady) {
+                if (DEBUG) Slog.d(TAG, "finishBoot all flags true");
                 if (mPendingBootBroadcast) {
                     updateUsbStateBroadcastIfNeeded(getAppliedFunctions(mCurrentFunctions));
                     mPendingBootBroadcast = false;
@@ -1441,14 +1491,12 @@
                 } else {
                     setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId);
                 }
-                if (mCurrentAccessory != null) {
-                    mUsbDeviceManager.getCurrentSettings().accessoryAttached(mCurrentAccessory);
-                    broadcastUsbAccessoryHandshake();
-                } else if (mPendingBootAccessoryHandshakeBroadcast) {
-                    broadcastUsbAccessoryHandshake();
+                if (!Flags.checkUserActionUnlocked()) {
+                    attachAccessoryAfterBoot();
                 }
-
-                mPendingBootAccessoryHandshakeBroadcast = false;
+                if (Flags.checkUserActionUnlocked() && mUserUnlockedAfterBoot) {
+                    attachAccessoryAfterBoot();
+                }
                 updateUsbNotification(false);
                 updateAdbNotification(false);
                 updateUsbFunctions();
diff --git a/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig b/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig
index cd96d76..a2d0efd 100644
--- a/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig
+++ b/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig
@@ -14,3 +14,10 @@
     description: "This flag enables binding to MtpService when in mtp/ptp modes"
     bug: "332256525"
 }
+
+flag {
+    name: "check_user_action_unlocked"
+    namespace: "usb"
+    description: "This flag checks if phone is unlocked after boot"
+    bug: "73654179"
+}
diff --git a/tests/PackageWatchdog/Android.bp b/tests/PackageWatchdog/Android.bp
index 91483eb..8be74eacc 100644
--- a/tests/PackageWatchdog/Android.bp
+++ b/tests/PackageWatchdog/Android.bp
@@ -37,7 +37,7 @@
         "truth",
     ] + select(soong_config_variable("ANDROID", "release_crashrecovery_module"), {
         "true": [
-            "service-crashrecovery.impl",
+            "service-crashrecovery-pre-jarjar",
             "framework-crashrecovery.impl",
         ],
         default: [],
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index 1bef5f8..1d4adc4 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -207,14 +207,13 @@
   Value* dst_value = dst_config_value->value.get();
   Value* src_value = src_config_value->value.get();
 
-  CollisionResult collision_result;
-  if (overlay) {
-    collision_result =
-        ResolveMergeCollision(override_styles_instead_of_overlaying, dst_value, src_value, pool);
-  } else {
-    collision_result =
-        ResourceTable::ResolveFlagCollision(dst_value->GetFlagStatus(), src_value->GetFlagStatus());
-    if (collision_result == CollisionResult::kConflict) {
+  CollisionResult collision_result =
+      ResourceTable::ResolveFlagCollision(dst_value->GetFlagStatus(), src_value->GetFlagStatus());
+  if (collision_result == CollisionResult::kConflict) {
+    if (overlay) {
+      collision_result =
+          ResolveMergeCollision(override_styles_instead_of_overlaying, dst_value, src_value, pool);
+    } else {
       collision_result = ResourceTable::ResolveValueCollision(dst_value, src_value);
     }
   }