Merge "Add scene transitions from Dream to Shade and Bouncer" into main
diff --git a/apct-tests/perftests/windowmanager/Android.bp b/apct-tests/perftests/windowmanager/Android.bp
index e9357f4..1175677 100644
--- a/apct-tests/perftests/windowmanager/Android.bp
+++ b/apct-tests/perftests/windowmanager/Android.bp
@@ -13,7 +13,7 @@
 // limitations under the License.
 
 package {
-    default_team: "trendy_team_input_framework",
+    default_team: "trendy_team_windowing_animations_transitions",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_license"
diff --git a/core/api/current.txt b/core/api/current.txt
index faa24bc6..06cf9a5 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8075,6 +8075,7 @@
     method @NonNull @WorkerThread public android.os.Bundle getApplicationRestrictions(@Nullable android.content.ComponentName, String);
     method @Deprecated @Nullable public String getApplicationRestrictionsManagingPackage(@NonNull android.content.ComponentName);
     method @RequiresPermission(anyOf={android.Manifest.permission.SET_TIME, "android.permission.QUERY_ADMIN_POLICY"}, conditional=true) public boolean getAutoTimeEnabled(@Nullable android.content.ComponentName);
+    method @FlaggedApi("android.app.admin.flags.set_auto_time_enabled_coexistence") @RequiresPermission(anyOf={android.Manifest.permission.SET_TIME, "android.permission.QUERY_ADMIN_POLICY"}, conditional=true) public int getAutoTimePolicy();
     method @Deprecated public boolean getAutoTimeRequired();
     method @RequiresPermission(anyOf={android.Manifest.permission.SET_TIME_ZONE, "android.permission.QUERY_ADMIN_POLICY"}, conditional=true) public boolean getAutoTimeZoneEnabled(@Nullable android.content.ComponentName);
     method @FlaggedApi("android.app.admin.flags.set_auto_time_zone_enabled_coexistence") @RequiresPermission(anyOf={android.Manifest.permission.SET_TIME_ZONE, "android.permission.QUERY_ADMIN_POLICY"}, conditional=true) public int getAutoTimeZonePolicy();
@@ -8233,6 +8234,7 @@
     method @WorkerThread public void setApplicationRestrictions(@Nullable android.content.ComponentName, String, android.os.Bundle);
     method @Deprecated public void setApplicationRestrictionsManagingPackage(@NonNull android.content.ComponentName, @Nullable String) throws android.content.pm.PackageManager.NameNotFoundException;
     method @RequiresPermission(value=android.Manifest.permission.SET_TIME, conditional=true) public void setAutoTimeEnabled(@Nullable android.content.ComponentName, boolean);
+    method @FlaggedApi("android.app.admin.flags.set_auto_time_enabled_coexistence") @RequiresPermission(value=android.Manifest.permission.SET_TIME, conditional=true) public void setAutoTimePolicy(int);
     method @Deprecated public void setAutoTimeRequired(@NonNull android.content.ComponentName, boolean);
     method @RequiresPermission(value=android.Manifest.permission.SET_TIME_ZONE, conditional=true) public void setAutoTimeZoneEnabled(@Nullable android.content.ComponentName, boolean);
     method @FlaggedApi("android.app.admin.flags.set_auto_time_zone_enabled_coexistence") @RequiresPermission(value=android.Manifest.permission.SET_TIME_ZONE, conditional=true) public void setAutoTimeZonePolicy(int);
@@ -8354,6 +8356,9 @@
     field public static final String ACTION_SET_NEW_PASSWORD = "android.app.action.SET_NEW_PASSWORD";
     field public static final String ACTION_START_ENCRYPTION = "android.app.action.START_ENCRYPTION";
     field public static final String ACTION_SYSTEM_UPDATE_POLICY_CHANGED = "android.app.action.SYSTEM_UPDATE_POLICY_CHANGED";
+    field @FlaggedApi("android.app.admin.flags.set_auto_time_enabled_coexistence") public static final int AUTO_TIME_DISABLED = 1; // 0x1
+    field @FlaggedApi("android.app.admin.flags.set_auto_time_enabled_coexistence") public static final int AUTO_TIME_ENABLED = 2; // 0x2
+    field @FlaggedApi("android.app.admin.flags.set_auto_time_enabled_coexistence") public static final int AUTO_TIME_NOT_CONTROLLED_BY_POLICY = 0; // 0x0
     field @FlaggedApi("android.app.admin.flags.set_auto_time_zone_enabled_coexistence") public static final int AUTO_TIME_ZONE_DISABLED = 1; // 0x1
     field @FlaggedApi("android.app.admin.flags.set_auto_time_zone_enabled_coexistence") public static final int AUTO_TIME_ZONE_ENABLED = 2; // 0x2
     field @FlaggedApi("android.app.admin.flags.set_auto_time_zone_enabled_coexistence") public static final int AUTO_TIME_ZONE_NOT_CONTROLLED_BY_POLICY = 0; // 0x0
@@ -20623,8 +20628,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
@@ -21081,6 +21092,7 @@
     method @Deprecated public android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface();
     method public android.view.View onCreateInputView();
     method protected void onCurrentInputMethodSubtypeChanged(android.view.inputmethod.InputMethodSubtype);
+    method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") public void onCustomImeSwitcherButtonRequestedVisible(boolean);
     method public void onDisplayCompletions(android.view.inputmethod.CompletionInfo[]);
     method public boolean onEvaluateFullscreenMode();
     method @CallSuper public boolean onEvaluateInputViewShown();
@@ -34662,7 +34674,10 @@
     method @NonNull public android.os.VibrationEffect.Composition addPrimitive(int);
     method @NonNull public android.os.VibrationEffect.Composition addPrimitive(int, @FloatRange(from=0.0f, to=1.0f) float);
     method @NonNull public android.os.VibrationEffect.Composition addPrimitive(int, @FloatRange(from=0.0f, to=1.0f) float, @IntRange(from=0) int);
+    method @FlaggedApi("android.os.vibrator.primitive_composition_absolute_delay") @NonNull public android.os.VibrationEffect.Composition addPrimitive(int, @FloatRange(from=0.0f, to=1.0f) float, @IntRange(from=0) int, int);
     method @NonNull public android.os.VibrationEffect compose();
+    field @FlaggedApi("android.os.vibrator.primitive_composition_absolute_delay") public static final int DELAY_TYPE_PAUSE = 0; // 0x0
+    field @FlaggedApi("android.os.vibrator.primitive_composition_absolute_delay") public static final int DELAY_TYPE_RELATIVE_START_OFFSET = 1; // 0x1
     field public static final int PRIMITIVE_CLICK = 1; // 0x1
     field public static final int PRIMITIVE_LOW_TICK = 8; // 0x8
     field public static final int PRIMITIVE_QUICK_FALL = 6; // 0x6
@@ -34685,12 +34700,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();
@@ -35041,6 +35053,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);
@@ -51401,6 +51423,7 @@
     method @Nullable public android.view.SurfaceControl.Transaction buildReparentTransaction(@NonNull android.view.SurfaceControl);
     method public default int getBufferTransformHint();
     method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") @NonNull public default android.window.InputTransferToken getInputTransferToken();
+    method @FlaggedApi("com.android.window.flags.jank_api") @NonNull public default android.view.SurfaceControl.OnJankDataListenerRegistration registerOnJankDataListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.SurfaceControl.OnJankDataListener);
     method public default void removeOnBufferTransformHintChangedListener(@NonNull android.view.AttachedSurfaceControl.OnBufferTransformHintChangedListener);
     method public default void setChildBoundingInsets(@NonNull android.graphics.Rect);
     method public default void setTouchableRegion(@Nullable android.graphics.Region);
@@ -51658,6 +51681,7 @@
     field public static final int DEADLINE = 13; // 0xd
     field public static final int DRAW_DURATION = 4; // 0x4
     field public static final int FIRST_DRAW_FRAME = 9; // 0x9
+    field @FlaggedApi("com.android.window.flags.jank_api") public static final int FRAME_TIMELINE_VSYNC_ID = 14; // 0xe
     field public static final int GPU_DURATION = 12; // 0xc
     field public static final int INPUT_HANDLING_DURATION = 1; // 0x1
     field public static final int INTENDED_VSYNC_TIMESTAMP = 10; // 0xa
@@ -53095,6 +53119,26 @@
     method @NonNull public android.view.SurfaceControl.Builder setParent(@Nullable android.view.SurfaceControl);
   }
 
+  @FlaggedApi("com.android.window.flags.jank_api") public static class SurfaceControl.JankData {
+    method public long getActualAppFrameTimeNanos();
+    method public int getJankType();
+    method public long getScheduledAppFrameTimeNanos();
+    method public long getVsyncId();
+    field public static final int JANK_APPLICATION = 2; // 0x2
+    field public static final int JANK_COMPOSER = 1; // 0x1
+    field public static final int JANK_NONE = 0; // 0x0
+    field public static final int JANK_OTHER = 4; // 0x4
+  }
+
+  @FlaggedApi("com.android.window.flags.jank_api") public static interface SurfaceControl.OnJankDataListener {
+    method public void onJankDataAvailable(@NonNull java.util.List<android.view.SurfaceControl.JankData>);
+  }
+
+  @FlaggedApi("com.android.window.flags.jank_api") public static class SurfaceControl.OnJankDataListenerRegistration {
+    method public void flush();
+    method public void removeAfter(long);
+  }
+
   public static class SurfaceControl.Transaction implements java.io.Closeable android.os.Parcelable {
     ctor public SurfaceControl.Transaction();
     method @NonNull public android.view.SurfaceControl.Transaction addTransactionCommittedListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.SurfaceControl.TransactionCommittedListener);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index c8ecfa9..1192713 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2065,6 +2065,29 @@
     method public boolean isAidlHal();
   }
 
+  public final class MediaCodec {
+    method @FlaggedApi("android.media.codec.codec_availability") @NonNull public static java.util.List<android.media.MediaCodec.GlobalResourceInfo> getGloballyAvailableResources();
+    method @FlaggedApi("android.media.codec.codec_availability") @NonNull public java.util.List<android.media.MediaCodec.InstanceResourceInfo> getRequiredResources();
+  }
+
+  public abstract static class MediaCodec.Callback {
+    method @FlaggedApi("android.media.codec.codec_availability") public void onRequiredResourcesChanged(@NonNull android.media.MediaCodec);
+  }
+
+  @FlaggedApi("android.media.codec.codec_availability") public static final class MediaCodec.GlobalResourceInfo {
+    ctor public MediaCodec.GlobalResourceInfo();
+    method public long getAvailable();
+    method public long getCapacity();
+    method @NonNull public String getName();
+  }
+
+  @FlaggedApi("android.media.codec.codec_availability") public static final class MediaCodec.InstanceResourceInfo {
+    ctor public MediaCodec.InstanceResourceInfo();
+    method @NonNull public String getName();
+    method public long getPerFrameCount();
+    method public long getStaticCount();
+  }
+
   public static final class MediaCodecInfo.VideoCapabilities.PerformancePoint {
     ctor public MediaCodecInfo.VideoCapabilities.PerformancePoint(int, int, int, int, @NonNull android.util.Size);
     ctor public MediaCodecInfo.VideoCapabilities.PerformancePoint(@NonNull android.media.MediaCodecInfo.VideoCapabilities.PerformancePoint, @NonNull android.util.Size);
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 1b707f7..ab75069 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -5300,7 +5300,6 @@
         if (!exported) {
             /*
             RuntimeException here = new RuntimeException("here");
-            here.fillInStackTrace();
             Slog.w(TAG, "Permission denied: checkComponentPermission() owningUid=" + owningUid,
                     here);
             */
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 60b8f80..cb7b115 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1961,12 +1961,8 @@
 
         @Override
         public void dumpCacheInfo(ParcelFileDescriptor pfd, String[] args) {
-            try {
-                PropertyInvalidatedCache.dumpCacheInfo(pfd, args);
-                BroadcastStickyCache.dump(pfd);
-            } finally {
-                IoUtils.closeQuietly(pfd);
-            }
+            PropertyInvalidatedCache.dumpCacheInfo(pfd, args);
+            IoUtils.closeQuietly(pfd);
         }
 
         private File getDatabasesDir(Context context) {
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index fb5a12b..e2479169 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -1835,7 +1835,6 @@
 
                 if (false) {
                     RuntimeException e = new RuntimeException("here");
-                    e.fillInStackTrace();
                     Log.w(TAG, "Getting drawable 0x" + Integer.toHexString(resId)
                                     + " from package " + packageName
                                     + ": app scale=" + r.getCompatibilityInfo().applicationScale
diff --git a/core/java/android/app/BroadcastStickyCache.java b/core/java/android/app/BroadcastStickyCache.java
deleted file mode 100644
index ea81731..0000000
--- a/core/java/android/app/BroadcastStickyCache.java
+++ /dev/null
@@ -1,269 +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 android.app;
-
-import android.annotation.IntRange;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.hardware.usb.UsbManager;
-import android.media.AudioManager;
-import android.net.ConnectivityManager;
-import android.net.TetheringManager;
-import android.net.nsd.NsdManager;
-import android.net.wifi.WifiManager;
-import android.net.wifi.p2p.WifiP2pManager;
-import android.os.ParcelFileDescriptor;
-import android.os.SystemProperties;
-import android.os.UpdateLock;
-import android.telephony.TelephonyManager;
-import android.util.ArrayMap;
-import android.util.IndentingPrintWriter;
-import android.view.WindowManagerPolicyConstants;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.FastPrintWriter;
-
-import java.io.FileOutputStream;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-
-/** @hide */
-public class BroadcastStickyCache {
-
-    private static final String[] CACHED_BROADCAST_ACTIONS = {
-            AudioManager.ACTION_HDMI_AUDIO_PLUG,
-            AudioManager.ACTION_HEADSET_PLUG,
-            AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED,
-            AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED,
-            AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION,
-            AudioManager.RINGER_MODE_CHANGED_ACTION,
-            ConnectivityManager.CONNECTIVITY_ACTION,
-            Intent.ACTION_BATTERY_CHANGED,
-            Intent.ACTION_DEVICE_STORAGE_FULL,
-            Intent.ACTION_DEVICE_STORAGE_LOW,
-            Intent.ACTION_SIM_STATE_CHANGED,
-            NsdManager.ACTION_NSD_STATE_CHANGED,
-            TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED,
-            TetheringManager.ACTION_TETHER_STATE_CHANGED,
-            UpdateLock.UPDATE_LOCK_CHANGED,
-            UsbManager.ACTION_USB_STATE,
-            WifiManager.ACTION_WIFI_SCAN_AVAILABILITY_CHANGED,
-            WifiManager.NETWORK_STATE_CHANGED_ACTION,
-            WifiManager.SUPPLICANT_STATE_CHANGED_ACTION,
-            WifiManager.WIFI_STATE_CHANGED_ACTION,
-            WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION,
-            WindowManagerPolicyConstants.ACTION_HDMI_PLUGGED,
-            "android.net.conn.INET_CONDITION_ACTION" // ConnectivityManager.INET_CONDITION_ACTION
-    };
-
-    @GuardedBy("sCachedStickyBroadcasts")
-    private static final ArrayList<CachedStickyBroadcast> sCachedStickyBroadcasts =
-            new ArrayList<>();
-
-    @GuardedBy("sCachedPropertyHandles")
-    private static final ArrayMap<String, SystemProperties.Handle> sCachedPropertyHandles =
-            new ArrayMap<>();
-
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    public static boolean useCache(@Nullable IntentFilter filter) {
-        if (!shouldCache(filter)) {
-            return false;
-        }
-        synchronized (sCachedStickyBroadcasts) {
-            final CachedStickyBroadcast cachedStickyBroadcast = getValueUncheckedLocked(filter);
-            if (cachedStickyBroadcast == null) {
-                return false;
-            }
-            final long version = cachedStickyBroadcast.propertyHandle.getLong(-1 /* def */);
-            return version > 0 && cachedStickyBroadcast.version == version;
-        }
-    }
-
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    public static void add(@Nullable IntentFilter filter, @Nullable Intent intent) {
-        if (!shouldCache(filter)) {
-            return;
-        }
-        synchronized (sCachedStickyBroadcasts) {
-            CachedStickyBroadcast cachedStickyBroadcast = getValueUncheckedLocked(filter);
-            if (cachedStickyBroadcast == null) {
-                final String key = getKey(filter.getAction(0));
-                final SystemProperties.Handle handle = SystemProperties.find(key);
-                final long version = handle == null ? -1 : handle.getLong(-1 /* def */);
-                if (version == -1) {
-                    return;
-                }
-                cachedStickyBroadcast = new CachedStickyBroadcast(filter, handle);
-                sCachedStickyBroadcasts.add(cachedStickyBroadcast);
-                cachedStickyBroadcast.intent = intent;
-                cachedStickyBroadcast.version = version;
-            } else {
-                cachedStickyBroadcast.intent = intent;
-                cachedStickyBroadcast.version = cachedStickyBroadcast.propertyHandle
-                        .getLong(-1 /* def */);
-            }
-        }
-    }
-
-    private static boolean shouldCache(@Nullable IntentFilter filter) {
-        if (!Flags.useStickyBcastCache()) {
-            return false;
-        }
-        if (filter == null || filter.safeCountActions() != 1) {
-            return false;
-        }
-        if (!ArrayUtils.contains(CACHED_BROADCAST_ACTIONS, filter.getAction(0))) {
-            return false;
-        }
-        return true;
-    }
-
-    @VisibleForTesting
-    @NonNull
-    public static String getKey(@NonNull String action) {
-        return "cache_key.system_server.sticky_bcast." + action;
-    }
-
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    @Nullable
-    public static Intent getIntentUnchecked(@NonNull IntentFilter filter) {
-        synchronized (sCachedStickyBroadcasts) {
-            final CachedStickyBroadcast cachedStickyBroadcast = getValueUncheckedLocked(filter);
-            return cachedStickyBroadcast.intent;
-        }
-    }
-
-    @GuardedBy("sCachedStickyBroadcasts")
-    @Nullable
-    private static CachedStickyBroadcast getValueUncheckedLocked(@NonNull IntentFilter filter) {
-        for (int i = sCachedStickyBroadcasts.size() - 1; i >= 0; --i) {
-            final CachedStickyBroadcast cachedStickyBroadcast = sCachedStickyBroadcasts.get(i);
-            if (IntentFilter.filterEquals(filter, cachedStickyBroadcast.filter)) {
-                return cachedStickyBroadcast;
-            }
-        }
-        return null;
-    }
-
-    public static void incrementVersion(@NonNull String action) {
-        if (!shouldIncrementVersion(action)) {
-            return;
-        }
-        final String key = getKey(action);
-        synchronized (sCachedPropertyHandles) {
-            SystemProperties.Handle handle = sCachedPropertyHandles.get(key);
-            final long version;
-            if (handle == null) {
-                handle = SystemProperties.find(key);
-                if (handle != null) {
-                    sCachedPropertyHandles.put(key, handle);
-                }
-            }
-            version = handle == null ? 0 : handle.getLong(0 /* def */);
-            SystemProperties.set(key, String.valueOf(version + 1));
-            if (handle == null) {
-                sCachedPropertyHandles.put(key, SystemProperties.find(key));
-            }
-        }
-    }
-
-    public static void incrementVersionIfExists(@NonNull String action) {
-        if (!shouldIncrementVersion(action)) {
-            return;
-        }
-        final String key = getKey(action);
-        synchronized (sCachedPropertyHandles) {
-            final SystemProperties.Handle handle = sCachedPropertyHandles.get(key);
-            if (handle == null) {
-                return;
-            }
-            final long version = handle.getLong(0 /* def */);
-            SystemProperties.set(key, String.valueOf(version + 1));
-        }
-    }
-
-    private static boolean shouldIncrementVersion(@NonNull String action) {
-        if (!Flags.useStickyBcastCache()) {
-            return false;
-        }
-        if (!ArrayUtils.contains(CACHED_BROADCAST_ACTIONS, action)) {
-            return false;
-        }
-        return true;
-    }
-
-    @VisibleForTesting
-    public static void clearForTest() {
-        synchronized (sCachedStickyBroadcasts) {
-            sCachedStickyBroadcasts.clear();
-        }
-        synchronized (sCachedPropertyHandles) {
-            sCachedPropertyHandles.clear();
-        }
-    }
-
-    public static void dump(@NonNull ParcelFileDescriptor pfd) {
-        if (!Flags.useStickyBcastCache()) {
-            return;
-        }
-        final PrintWriter pw = new FastPrintWriter(new FileOutputStream(pfd.getFileDescriptor()));
-        synchronized (sCachedStickyBroadcasts) {
-            dumpLocked(pw);
-        }
-        pw.flush();
-    }
-
-    @GuardedBy("sCachedStickyBroadcasts")
-    private static void dumpLocked(@NonNull PrintWriter pw) {
-        final IndentingPrintWriter ipw = new IndentingPrintWriter(
-                pw, "  " /* singleIndent */, "  " /* prefix */);
-        ipw.println("Cached sticky broadcasts:");
-        ipw.increaseIndent();
-        final int count = sCachedStickyBroadcasts.size();
-        if (count == 0) {
-            ipw.println("<empty>");
-        } else {
-            for (int i = 0; i < count; ++i) {
-                final CachedStickyBroadcast cachedStickyBroadcast = sCachedStickyBroadcasts.get(i);
-                ipw.print("Entry #"); ipw.print(i); ipw.println(":");
-                ipw.increaseIndent();
-                ipw.print("filter="); ipw.println(cachedStickyBroadcast.filter.toLongString());
-                ipw.print("intent="); ipw.println(cachedStickyBroadcast.intent);
-                ipw.print("version="); ipw.println(cachedStickyBroadcast.version);
-                ipw.print("handle="); ipw.println(cachedStickyBroadcast.propertyHandle);
-                ipw.decreaseIndent();
-            }
-        }
-        ipw.decreaseIndent();
-    }
-
-    private static final class CachedStickyBroadcast {
-        @NonNull public final IntentFilter filter;
-        @Nullable public Intent intent;
-        @IntRange(from = 0) public long version;
-        @NonNull public final SystemProperties.Handle propertyHandle;
-
-        CachedStickyBroadcast(@NonNull IntentFilter filter,
-                @NonNull SystemProperties.Handle propertyHandle) {
-            this.filter = filter;
-            this.propertyHandle = propertyHandle;
-        }
-    }
-}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 3ae60d71..cd56957 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1922,19 +1922,10 @@
             }
         }
         try {
-            final Intent intent;
-            if (receiver == null && BroadcastStickyCache.useCache(filter)) {
-                intent = BroadcastStickyCache.getIntentUnchecked(filter);
-            } else {
-                intent = ActivityManager.getService().registerReceiverWithFeature(
-                        mMainThread.getApplicationThread(), mBasePackageName, getAttributionTag(),
-                        AppOpsManager.toReceiverId(receiver), rd, filter, broadcastPermission,
-                        userId,
-                        flags);
-                if (receiver == null) {
-                    BroadcastStickyCache.add(filter, intent);
-                }
-            }
+            final Intent intent = ActivityManager.getService().registerReceiverWithFeature(
+                    mMainThread.getApplicationThread(), mBasePackageName, getAttributionTag(),
+                    AppOpsManager.toReceiverId(receiver), rd, filter, broadcastPermission, userId,
+                    flags);
             if (intent != null) {
                 intent.setExtrasClassLoader(getClassLoader());
                 // TODO: determine at registration time if caller is
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index b8233bc..3d85ea6 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -1660,7 +1660,6 @@
                         }
                         RuntimeException ex = new IllegalArgumentException(
                                 "Originally unregistered here:");
-                        ex.fillInStackTrace();
                         rd.setUnregisterLocation(ex);
                         holder.put(r, rd);
                     }
@@ -1860,7 +1859,6 @@
             mInstrumentation = instrumentation;
             mRegistered = registered;
             mLocation = new IntentReceiverLeaked(null);
-            mLocation.fillInStackTrace();
         }
 
         void validate(Context context, Handler activityThread) {
@@ -2000,7 +1998,6 @@
                         }
                         RuntimeException ex = new IllegalArgumentException(
                                 "Originally unbound here:");
-                        ex.fillInStackTrace();
                         sd.setUnbindLocation(ex);
                         holder.put(c, sd);
                     }
@@ -2076,7 +2073,6 @@
             mActivityThread = activityThread;
             mActivityExecutor = null;
             mLocation = new ServiceConnectionLeaked(null);
-            mLocation.fillInStackTrace();
             mFlags = flags;
         }
 
@@ -2088,7 +2084,6 @@
             mActivityThread = null;
             mActivityExecutor = activityExecutor;
             mLocation = new ServiceConnectionLeaked(null);
-            mLocation.fillInStackTrace();
             mFlags = flags;
         }
 
diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java
index e2de716..a70d493 100644
--- a/core/java/android/app/LoaderManager.java
+++ b/core/java/android/app/LoaderManager.java
@@ -83,7 +83,7 @@
          * transactions while in this call, since it can happen after an
          * activity's state is saved.  See {@link FragmentManager#beginTransaction()
          * FragmentManager.openTransaction()} for further discussion on this.
-         * 
+         *
          * <p>This function is guaranteed to be called prior to the release of
          * the last data that was supplied for this Loader.  At this point
          * you should remove all use of the old data (since it will be released
@@ -127,7 +127,7 @@
          */
         public void onLoaderReset(Loader<D> loader);
     }
-    
+
     /**
      * Ensures a loader is initialized and active.  If the loader doesn't
      * already exist, one is created and (if the activity/fragment is currently
@@ -228,7 +228,7 @@
     boolean mStarted;
     boolean mRetaining;
     boolean mRetainingStarted;
-    
+
     boolean mCreatingLoader;
     private FragmentHostCallback mHost;
 
@@ -249,13 +249,13 @@
         boolean mListenerRegistered;
 
         LoaderInfo mPendingLoader;
-        
+
         public LoaderInfo(int id, Bundle args, LoaderManager.LoaderCallbacks<Object> callbacks) {
             mId = id;
             mArgs = args;
             mCallbacks = callbacks;
         }
-        
+
         void start() {
             if (mRetaining && mRetainingStarted) {
                 // Our owner is started, but we were being retained from a
@@ -271,7 +271,7 @@
             }
 
             mStarted = true;
-            
+
             if (DEBUG) Log.v(TAG, "  Starting: " + this);
             if (mLoader == null && mCallbacks != null) {
                mLoader = mCallbacks.onCreateLoader(mId, mArgs);
@@ -291,7 +291,7 @@
                 mLoader.startLoading();
             }
         }
-        
+
         void retain() {
             if (DEBUG) Log.v(TAG, "  Retaining: " + this);
             mRetaining = true;
@@ -299,7 +299,7 @@
             mStarted = false;
             mCallbacks = null;
         }
-        
+
         void finishRetain() {
             if (mRetaining) {
                 if (DEBUG) Log.v(TAG, "  Finished Retaining: " + this);
@@ -324,7 +324,7 @@
                 callOnLoadFinished(mLoader, mData);
             }
         }
-        
+
         void reportStart() {
             if (mStarted) {
                 if (mReportNextStart) {
@@ -430,7 +430,7 @@
         @Override
         public void onLoadComplete(Loader<Object> loader, Object data) {
             if (DEBUG) Log.v(TAG, "onLoadComplete: " + this);
-            
+
             if (mDestroyed) {
                 if (DEBUG) Log.v(TAG, "  Ignoring load complete -- destroyed");
                 return;
@@ -442,7 +442,7 @@
                 if (DEBUG) Log.v(TAG, "  Ignoring load complete -- not active");
                 return;
             }
-            
+
             LoaderInfo pending = mPendingLoader;
             if (pending != null) {
                 // There is a new request pending and we were just
@@ -455,7 +455,7 @@
                 installLoader(pending);
                 return;
             }
-            
+
             // Notify of the new data so the app can switch out the old data before
             // we try to destroy it.
             if (mData != data || !mHaveData) {
@@ -503,7 +503,7 @@
                 mDeliveredData = true;
             }
         }
-        
+
         @Override
         public String toString() {
             StringBuilder sb = new StringBuilder(64);
@@ -543,13 +543,13 @@
             }
         }
     }
-    
+
     LoaderManagerImpl(String who, FragmentHostCallback host, boolean started) {
         mWho = who;
         mHost = host;
         mStarted = started;
     }
-    
+
     void updateHostController(FragmentHostCallback host) {
         mHost = host;
     }
@@ -557,7 +557,7 @@
     public FragmentHostCallback getFragmentHostCallback() {
         return mHost;
     }
-    
+
     private LoaderInfo createLoader(int id, Bundle args,
             LoaderManager.LoaderCallbacks<Object> callback) {
         LoaderInfo info = new LoaderInfo(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
@@ -565,7 +565,7 @@
         info.mLoader = (Loader<Object>)loader;
         return info;
     }
-    
+
     private LoaderInfo createAndInstallLoader(int id, Bundle args,
             LoaderManager.LoaderCallbacks<Object> callback) {
         try {
@@ -577,7 +577,7 @@
             mCreatingLoader = false;
         }
     }
-    
+
     void installLoader(LoaderInfo info) {
         mLoaders.put(info.mId, info);
         if (mStarted) {
@@ -587,23 +587,23 @@
             info.start();
         }
     }
-    
+
     /**
      * Call to initialize a particular ID with a Loader.  If this ID already
      * has a Loader associated with it, it is left unchanged and any previous
      * callbacks replaced with the newly provided ones.  If there is not currently
      * a Loader for the ID, a new one is created and started.
-     * 
+     *
      * <p>This function should generally be used when a component is initializing,
      * to ensure that a Loader it relies on is created.  This allows it to re-use
      * an existing Loader's data if there already is one, so that for example
      * when an {@link Activity} is re-created after a configuration change it
      * does not need to re-create its loaders.
-     * 
+     *
      * <p>Note that in the case where an existing Loader is re-used, the
      * <var>args</var> given here <em>will be ignored</em> because you will
      * continue using the previous Loader.
-     * 
+     *
      * @param id A unique (to this LoaderManager instance) identifier under
      * which to manage the new Loader.
      * @param args Optional arguments that will be propagated to
@@ -617,9 +617,9 @@
         if (mCreatingLoader) {
             throw new IllegalStateException("Called while creating a loader");
         }
-        
+
         LoaderInfo info = mLoaders.get(id);
-        
+
         if (DEBUG) Log.v(TAG, "initLoader in " + this + ": args=" + args);
 
         if (info == null) {
@@ -630,30 +630,30 @@
             if (DEBUG) Log.v(TAG, "  Re-using existing loader " + info);
             info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
         }
-        
+
         if (info.mHaveData && mStarted) {
             // If the loader has already generated its data, report it now.
             info.callOnLoadFinished(info.mLoader, info.mData);
         }
-        
+
         return (Loader<D>)info.mLoader;
     }
-    
+
     /**
      * Call to re-create the Loader associated with a particular ID.  If there
      * is currently a Loader associated with this ID, it will be
      * canceled/stopped/destroyed as appropriate.  A new Loader with the given
      * arguments will be created and its data delivered to you once available.
-     * 
+     *
      * <p>This function does some throttling of Loaders.  If too many Loaders
      * have been created for the given ID but not yet generated their data,
      * new calls to this function will create and return a new Loader but not
      * actually start it until some previous loaders have completed.
-     * 
+     *
      * <p>After calling this function, any previous Loaders associated with
      * this ID will be considered invalid, and you will receive no further
      * data updates from them.
-     * 
+     *
      * @param id A unique (to this LoaderManager instance) identifier under
      * which to manage the new Loader.
      * @param args Optional arguments that will be propagated to
@@ -667,7 +667,7 @@
         if (mCreatingLoader) {
             throw new IllegalStateException("Called while creating a loader");
         }
-        
+
         LoaderInfo info = mLoaders.get(id);
         if (DEBUG) Log.v(TAG, "restartLoader in " + this + ": args=" + args);
         if (info != null) {
@@ -706,7 +706,7 @@
                             info.mPendingLoader = null;
                         }
                         if (DEBUG) Log.v(TAG, "  Enqueuing as new pending loader");
-                        info.mPendingLoader = createLoader(id, args, 
+                        info.mPendingLoader = createLoader(id, args,
                                 (LoaderManager.LoaderCallbacks<Object>)callback);
                         return (Loader<D>)info.mPendingLoader.mLoader;
                     }
@@ -719,11 +719,11 @@
                 mInactiveLoaders.put(id, info);
             }
         }
-        
+
         info = createAndInstallLoader(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
         return (Loader<D>)info.mLoader;
     }
-    
+
     /**
      * Rip down, tear apart, shred to pieces a current Loader ID.  After returning
      * from this function, any Loader objects associated with this ID are
@@ -735,7 +735,7 @@
         if (mCreatingLoader) {
             throw new IllegalStateException("Called while creating a loader");
         }
-        
+
         if (DEBUG) Log.v(TAG, "destroyLoader in " + this + " of " + id);
         int idx = mLoaders.indexOfKey(id);
         if (idx >= 0) {
@@ -763,7 +763,7 @@
         if (mCreatingLoader) {
             throw new IllegalStateException("Called while creating a loader");
         }
-        
+
         LoaderInfo loaderInfo = mLoaders.get(id);
         if (loaderInfo != null) {
             if (loaderInfo.mPendingLoader != null) {
@@ -773,16 +773,15 @@
         }
         return null;
     }
- 
+
     void doStart() {
         if (DEBUG) Log.v(TAG, "Starting in " + this);
         if (mStarted) {
             RuntimeException e = new RuntimeException("here");
-            e.fillInStackTrace();
             Log.w(TAG, "Called doStart when already started: " + this, e);
             return;
         }
-        
+
         mStarted = true;
 
         // Call out to sub classes so they can start their loaders
@@ -791,12 +790,11 @@
             mLoaders.valueAt(i).start();
         }
     }
-    
+
     void doStop() {
         if (DEBUG) Log.v(TAG, "Stopping in " + this);
         if (!mStarted) {
             RuntimeException e = new RuntimeException("here");
-            e.fillInStackTrace();
             Log.w(TAG, "Called doStop when not started: " + this, e);
             return;
         }
@@ -806,12 +804,11 @@
         }
         mStarted = false;
     }
-    
+
     void doRetain() {
         if (DEBUG) Log.v(TAG, "Retaining in " + this);
         if (!mStarted) {
             RuntimeException e = new RuntimeException("here");
-            e.fillInStackTrace();
             Log.w(TAG, "Called doRetain when not started: " + this, e);
             return;
         }
@@ -822,7 +819,7 @@
             mLoaders.valueAt(i).retain();
         }
     }
-    
+
     void finishRetain() {
         if (mRetaining) {
             if (DEBUG) Log.v(TAG, "Finished Retaining in " + this);
@@ -833,7 +830,7 @@
             }
         }
     }
-    
+
     void doReportNextStart() {
         for (int i = mLoaders.size()-1; i >= 0; i--) {
             mLoaders.valueAt(i).mReportNextStart = true;
@@ -854,7 +851,7 @@
             }
             mLoaders.clear();
         }
-        
+
         if (DEBUG) Log.v(TAG, "Destroying Inactive in " + this);
         for (int i = mInactiveLoaders.size()-1; i >= 0; i--) {
             mInactiveLoaders.valueAt(i).destroy();
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index f702b85..087e246 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -1186,7 +1186,6 @@
         synchronized (mLock) {
             if (DEBUG) {
                 Throwable here = new Throwable();
-                here.fillInStackTrace();
                 Slog.w(TAG, "!! Create resources for key=" + key, here);
             }
 
@@ -1207,7 +1206,6 @@
         synchronized (mLock) {
             if (DEBUG) {
                 Throwable here = new Throwable();
-                here.fillInStackTrace();
                 Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here);
             }
 
@@ -1351,7 +1349,6 @@
 
                 if (DEBUG) {
                     Throwable here = new Throwable();
-                    here.fillInStackTrace();
                     Slog.d(TAG, "updating resources override for activity=" + activityToken
                             + " from oldConfig="
                             + Configuration.resourceQualifierString(oldConfig)
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index 637187e..5ed1f4e 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -177,10 +177,6 @@
         {
             "file_patterns": ["(/|^)AppOpsManager.java"],
             "name": "CtsAppOpsTestCases"
-        },
-        {
-            "file_patterns": ["(/|^)BroadcastStickyCache.java"],
-            "name": "BroadcastUnitTests"
         }
     ]
 }
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index bff77f9..4e68b5a 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -8920,12 +8920,9 @@
     /**
      * Called by a device owner, a profile owner for the primary user or a profile
      * owner of an organization-owned managed profile to turn auto time on and off.
-     * Callers are recommended to use {@link UserManager#DISALLOW_CONFIG_DATE_TIME}
-     * to prevent the user from changing this setting.
      * <p>
-     * If user restriction {@link UserManager#DISALLOW_CONFIG_DATE_TIME} is used,
-     * no user will be able set the date and time. Instead, the network date
-     * and time will be used.
+     * Callers are recommended to use {@link UserManager#DISALLOW_CONFIG_DATE_TIME} to prevent the
+     * user from changing this setting, that way no user will be able set the date and time zone.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the
      *              caller is not a device admin.
@@ -8938,7 +8935,13 @@
         throwIfParentInstance("setAutoTimeEnabled");
         if (mService != null) {
             try {
-                mService.setAutoTimeEnabled(admin, mContext.getPackageName(), enabled);
+                if (Flags.setAutoTimeEnabledCoexistence()) {
+                    mService.setAutoTimePolicy(mContext.getPackageName(),
+                            enabled ? DevicePolicyManager.AUTO_TIME_ENABLED
+                                    : DevicePolicyManager.AUTO_TIME_DISABLED);
+                } else {
+                    mService.setAutoTimeEnabled(admin, mContext.getPackageName(), enabled);
+                }
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -8968,6 +8971,97 @@
     }
 
     /**
+     * Specifies that the auto time state is not controlled by device policy.
+     *
+     * @see #setAutoTimePolicy(ComponentName, int)
+     */
+    @FlaggedApi(Flags.FLAG_SET_AUTO_TIME_ENABLED_COEXISTENCE)
+    public static final int AUTO_TIME_NOT_CONTROLLED_BY_POLICY = 0;
+
+    /**
+     * Specifies the "disabled" auto time state.
+     *
+     * @see #setAutoTimePolicy(ComponentName, int)
+     */
+    @FlaggedApi(Flags.FLAG_SET_AUTO_TIME_ENABLED_COEXISTENCE)
+    public static final int AUTO_TIME_DISABLED = 1;
+
+    /**
+     * Specifies the "enabled" auto time state.
+     *
+     * @see #setAutoTimePolicy(ComponentName, int)
+     */
+    @FlaggedApi(Flags.FLAG_SET_AUTO_TIME_ENABLED_COEXISTENCE)
+    public static final int AUTO_TIME_ENABLED = 2;
+
+    /**
+     * Flags supplied to {@link #setAutoTimePolicy}(ComponentName, int)}.
+     *
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "AUTO_TIME_" }, value = {
+            AUTO_TIME_NOT_CONTROLLED_BY_POLICY,
+            AUTO_TIME_DISABLED,
+            AUTO_TIME_ENABLED
+    })
+    public @interface AutoTimePolicy {}
+
+    /**
+     * Called by a device owner, a profile owner for the primary user or a profile owner of an
+     * organization-owned managed profile to turn auto time on and off i.e. Whether time should be
+     * obtained automatically from the network or not.
+     * <p>
+     * Callers are recommended to use {@link UserManager#DISALLOW_CONFIG_DATE_TIME} to prevent the
+     * user from changing this setting, that way no user will be able set the date and time zone.
+     *
+     * @param policy The desired state among {@link #AUTO_TIME_ENABLED} to enable,
+     *              {@link #AUTO_TIME_DISABLED} to disable and
+     *              {@link #AUTO_TIME_NOT_CONTROLLED_BY_POLICY} to unset the policy.
+     * @throws SecurityException if caller is not a device owner, a profile owner for the
+     * primary user, or a profile owner of an organization-owned managed profile, or if the caller
+     * does not hold the required permission.
+     */
+    @SupportsCoexistence
+    @RequiresPermission(value = SET_TIME, conditional = true)
+    @FlaggedApi(Flags.FLAG_SET_AUTO_TIME_ENABLED_COEXISTENCE)
+    public void setAutoTimePolicy(@AutoTimePolicy int policy) {
+        throwIfParentInstance("setAutoTimePolicy");
+        if (mService != null) {
+            try {
+                mService.setAutoTimePolicy(mContext.getPackageName(), policy);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Returns current auto time policy's state.
+     *
+     * @return One of {@link #AUTO_TIME_ENABLED} if enabled, {@link #AUTO_TIME_DISABLED} if disabled
+     *              and {@link #AUTO_TIME_NOT_CONTROLLED_BY_POLICY} if it's not controlled by
+     *              policy.
+     * @throws SecurityException if caller is not a device owner, a profile owner for the
+     * primary user, or a profile owner of an organization-owned managed profile, or if the caller
+     * does not hold the required permission.
+     */
+    @SupportsCoexistence
+    @RequiresPermission(anyOf = {SET_TIME, QUERY_ADMIN_POLICY}, conditional = true)
+    @FlaggedApi(Flags.FLAG_SET_AUTO_TIME_ENABLED_COEXISTENCE)
+    public @AutoTimePolicy int getAutoTimePolicy() {
+        throwIfParentInstance("getAutoTimePolicy");
+        if (mService != null) {
+            try {
+                return mService.getAutoTimePolicy(mContext.getPackageName());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return DevicePolicyManager.AUTO_TIME_NOT_CONTROLLED_BY_POLICY;
+    }
+
+    /**
      * Called by a device owner, a profile owner for the primary user or a profile
      * owner of an organization-owned managed profile to turn auto time zone on and off.
      * <p>
@@ -12102,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>
@@ -12187,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 0b8f538..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);
 
@@ -375,6 +376,9 @@
     void setAutoTimeEnabled(in ComponentName who, String callerPackageName, boolean enabled);
     boolean getAutoTimeEnabled(in ComponentName who, String callerPackageName);
 
+    void setAutoTimePolicy(String callerPackageName, int policy);
+    int getAutoTimePolicy(String callerPackageName);
+
     void setAutoTimeZoneEnabled(in ComponentName who, String callerPackageName, boolean enabled);
     boolean getAutoTimeZoneEnabled(in ComponentName who, String callerPackageName);
 
diff --git a/core/java/android/app/jank/JankDataProcessor.java b/core/java/android/app/jank/JankDataProcessor.java
index 3783a5f..7525d04 100644
--- a/core/java/android/app/jank/JankDataProcessor.java
+++ b/core/java/android/app/jank/JankDataProcessor.java
@@ -70,8 +70,8 @@
             for (int j = 0; j < mPendingStates.size(); j++) {
                 StateData pendingState = mPendingStates.get(j);
                 // This state was active during the frame
-                if (frame.frameVsyncId >= pendingState.mVsyncIdStart
-                        && frame.frameVsyncId <= pendingState.mVsyncIdEnd) {
+                if (frame.getVsyncId() >= pendingState.mVsyncIdStart
+                        && frame.getVsyncId() <= pendingState.mVsyncIdEnd) {
                     recordFrameCount(frame, pendingState, activityName, appUid);
 
                     pendingState.mProcessed = true;
@@ -131,14 +131,14 @@
             mPendingJankStats.put(stateData.mStateDataKey, jankStats);
         }
         // This state has already been accounted for
-        if (jankStats.processedVsyncId == frameData.frameVsyncId) return;
+        if (jankStats.processedVsyncId == frameData.getVsyncId()) return;
 
         jankStats.mTotalFrames += 1;
-        if (frameData.jankType == JankData.JANK_APPLICATION) {
+        if ((frameData.getJankType() & JankData.JANK_APPLICATION) != 0) {
             jankStats.mJankyFrames += 1;
         }
-        jankStats.recordFrameOverrun(frameData.actualAppFrameTimeNs);
-        jankStats.processedVsyncId = frameData.frameVsyncId;
+        jankStats.recordFrameOverrun(frameData.getActualAppFrameTimeNanos());
+        jankStats.processedVsyncId = frameData.getVsyncId();
 
     }
 
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index 6934e98..a487da2 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -268,3 +268,10 @@
   description: "Adds UI for NAS classification of notifications"
   bug: "367996732"
 }
+
+flag {
+  name: "no_sbnholder"
+  namespace: "systemui"
+  description: "removes sbnholder from NLS"
+  bug: "362981561"
+}
diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java
index 964a8be..a816294 100644
--- a/core/java/android/content/BroadcastReceiver.java
+++ b/core/java/android/content/BroadcastReceiver.java
@@ -356,7 +356,6 @@
             }
             RuntimeException e = new RuntimeException(
                     "BroadcastReceiver trying to return result during a non-ordered broadcast");
-            e.fillInStackTrace();
             Log.e("BroadcastReceiver", e.getMessage(), e);
         }
     }
@@ -768,7 +767,6 @@
         }
         RuntimeException e = new RuntimeException(
                 "BroadcastReceiver trying to return result during a non-ordered broadcast");
-        e.fillInStackTrace();
         Log.e("BroadcastReceiver", e.getMessage(), e);
     }
 }
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 6fd4d01..4551bd5 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -973,9 +973,9 @@
      * Open an asset using ACCESS_STREAMING mode.  This provides access to
      * files that have been bundled with an application as assets -- that is,
      * files placed in to the "assets" directory.
-     * 
+     *
      * @param fileName The name of the asset to open.  This name can be hierarchical.
-     * 
+     *
      * @see #open(String, int)
      * @see #list
      */
@@ -988,10 +988,10 @@
      * read its contents.  This provides access to files that have been bundled
      * with an application as assets -- that is, files placed in to the
      * "assets" directory.
-     * 
+     *
      * @param fileName The name of the asset to open.  This name can be hierarchical.
      * @param accessMode Desired access mode for retrieving the data.
-     * 
+     *
      * @see #ACCESS_UNKNOWN
      * @see #ACCESS_STREAMING
      * @see #ACCESS_RANDOM
@@ -1037,14 +1037,14 @@
 
     /**
      * Return a String array of all the assets at the given path.
-     * 
+     *
      * @param path A relative path within the assets, i.e., "docs/home.html".
-     * 
+     *
      * @return String[] Array of strings, one for each asset.  These file
      *         names are relative to 'path'.  You can open the file by
      *         concatenating 'path' and a name in the returned string (via
      *         File) and passing that to open().
-     * 
+     *
      * @see #open
      */
     public @Nullable String[] list(@NonNull String path) throws IOException {
@@ -1167,20 +1167,20 @@
             return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]);
         }
     }
-    
+
     /**
      * Retrieve a parser for a compiled XML file.
-     * 
+     *
      * @param fileName The name of the file to retrieve.
      */
     public @NonNull XmlResourceParser openXmlResourceParser(@NonNull String fileName)
             throws IOException {
         return openXmlResourceParser(0, fileName);
     }
-    
+
     /**
      * Retrieve a parser for a compiled XML file.
-     * 
+     *
      * @param cookie Identifier of the package to be opened.
      * @param fileName The name of the file to retrieve.
      */
@@ -1200,7 +1200,7 @@
 
     /**
      * Retrieve a non-asset as a compiled XML file.  Not for use by applications.
-     * 
+     *
      * @param fileName The name of the file to retrieve.
      * @hide
      */
@@ -1211,7 +1211,7 @@
     /**
      * Retrieve a non-asset as a compiled XML file.  Not for use by
      * applications.
-     * 
+     *
      * @param cookie Identifier of the package to be opened.
      * @param fileName Name of the asset to retrieve.
      * @hide
@@ -1675,7 +1675,6 @@
                 mRefStacks = new HashMap<>();
             }
             RuntimeException ex = new RuntimeException();
-            ex.fillInStackTrace();
             mRefStacks.put(id, ex);
         }
         mNumRefs++;
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/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/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 70a1909..dadb5c38 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -55,6 +55,7 @@
 import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_OTHER;
 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.ctrlShiftShortcut;
 import static android.view.inputmethod.Flags.predictiveBackIme;
 
@@ -4392,6 +4393,39 @@
     }
 
     /**
+     * Called when the requested visibility of a custom IME Switcher button changes.
+     *
+     * <p>When the system provides an IME navigation bar, it may decide to show an IME Switcher
+     * button inside this bar. However, the IME can request hiding the bar provided by the system
+     * with {@code getWindowInsetsController().hide(captionBar())} (the IME navigation bar provides
+     * {@link Type#captionBar() captionBar} insets to the IME window). If the request is successful,
+     * then it becomes the IME's responsibility to provide a custom IME Switcher button in its
+     * input view, with equivalent functionality.</p>
+     *
+     * <p>This custom button is only requested to be visible when the system provides the IME
+     * navigation bar, both the bar and the IME Switcher button inside it should be visible,
+     * but the IME successfully requested to hide the bar. This does not depend on the current
+     * visibility of the IME. It could be called with {@code true} while the IME is hidden, in
+     * which case the IME should prepare to show the button as soon as the IME itself is shown.</p>
+     *
+     * <p>This is only called when the requested visibility changes. The default value is
+     * {@code false} and as such, this will not be called initially if the resulting value is
+     * {@code false}.</p>
+     *
+     * <p>This can be called at any time after {@link #onCreate}, even if the IME is not currently
+     * visible. However, this is not guaranteed to be called before the IME is shown, as it depends
+     * on when the IME requested hiding the IME navigation bar. If the request is sent during
+     * the showing flow (e.g. during {@link #onStartInputView}), this will be called shortly after
+     * {@link #onWindowShown}, but before the first IME frame is drawn.</p>
+     *
+     * @param visible whether the button is requested visible or not.
+     */
+    @FlaggedApi(FLAG_IME_SWITCHER_REVAMP_API)
+    public void onCustomImeSwitcherButtonRequestedVisible(boolean visible) {
+        // Intentionally empty
+    }
+
+    /**
      * Called when the IME switch button was clicked from the client. Depending on the number of
      * enabled IME subtypes, this will either switch to the next IME/subtype, or show the input
      * method picker dialog.
diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java
index b08454d..38be8d9 100644
--- a/core/java/android/inputmethodservice/NavigationBarController.java
+++ b/core/java/android/inputmethodservice/NavigationBarController.java
@@ -41,6 +41,7 @@
 import android.view.WindowInsetsController.Appearance;
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
+import android.view.inputmethod.Flags;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.FrameLayout;
 
@@ -178,6 +179,9 @@
 
         private boolean mDrawLegacyNavigationBarBackground;
 
+        /** Whether a custom IME Switcher button should be visible. */
+        private boolean mCustomImeSwitcherVisible;
+
         private final Rect mTempRect = new Rect();
         private final int[] mTempPos = new int[2];
 
@@ -265,6 +269,7 @@
                     // IME navigation bar.
                     boolean visible = insets.isVisible(captionBar());
                     mNavigationBarFrame.setVisibility(visible ? View.VISIBLE : View.GONE);
+                    checkCustomImeSwitcherVisibility();
                 }
                 return view.onApplyWindowInsets(insets);
             });
@@ -491,6 +496,8 @@
                     mShouldShowImeSwitcherWhenImeIsShown;
             mShouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherWhenImeIsShown;
 
+            checkCustomImeSwitcherVisibility();
+
             mService.mWindow.getWindow().getDecorView().getWindowInsetsController()
                     .setImeCaptionBarInsetsHeight(getImeCaptionBarHeight(imeDrawsImeNavBar));
 
@@ -616,12 +623,33 @@
                     && mNavigationBarFrame.getVisibility() == View.VISIBLE;
         }
 
+        /**
+         * Checks if a custom IME Switcher button should be visible, and notifies the IME when this
+         * state changes. This can only be {@code true} if three conditions are met:
+         *
+         * <li>The IME should draw the IME navigation bar.</li>
+         * <li>The IME Switcher button should be visible when the IME is visible.</li>
+         * <li>The IME navigation bar should be visible, but was requested hidden by the IME.</li>
+         */
+        private void checkCustomImeSwitcherVisibility() {
+            if (!Flags.imeSwitcherRevampApi()) {
+                return;
+            }
+            final boolean visible = mImeDrawsImeNavBar && mShouldShowImeSwitcherWhenImeIsShown
+                    && mNavigationBarFrame != null && !isShown();
+            if (visible != mCustomImeSwitcherVisible) {
+                mCustomImeSwitcherVisible = visible;
+                mService.onCustomImeSwitcherButtonRequestedVisible(mCustomImeSwitcherVisible);
+            }
+        }
+
         @Override
         public String toDebugString() {
             return "{mImeDrawsImeNavBar=" + mImeDrawsImeNavBar
                     + " mNavigationBarFrame=" + mNavigationBarFrame
                     + " mShouldShowImeSwitcherWhenImeIsShown="
                     + mShouldShowImeSwitcherWhenImeIsShown
+                    + " mCustomImeSwitcherVisible="  + mCustomImeSwitcherVisible
                     + " mAppearance=0x" + Integer.toHexString(mAppearance)
                     + " mDarkIntensity=" + mDarkIntensity
                     + " mDrawLegacyNavigationBarBackground=" + mDrawLegacyNavigationBarBackground
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 102bdd0..c2e9260 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -32,6 +32,7 @@
 import android.content.Context;
 import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 import android.sdk.Flags;
+import android.sysprop.BackportedFixesProperties;
 import android.sysprop.DeviceProperties;
 import android.sysprop.SocProperties;
 import android.sysprop.TelephonyProperties;
@@ -1612,12 +1613,25 @@
      * is not applicable on this device,
      * otherwise {@link #BACKPORTED_FIX_STATUS_UNKNOWN}.
      */
-
     @FlaggedApi(android.os.Flags.FLAG_API_FOR_BACKPORTED_FIXES)
     public static @BackportedFixStatus int getBackportedFixStatus(long id) {
-        // TODO: b/308461809 - query aliases from system prop
-        // TODO: b/372518979 - use backported fix datastore.
-        return BACKPORTED_FIX_STATUS_UNKNOWN;
+        if (id <= 0 || id > 1023) {
+            return BACKPORTED_FIX_STATUS_UNKNOWN;
+        }
+        return isBitSet(BackportedFixesProperties.alias_bitset(), (int) id)
+                ? BACKPORTED_FIX_STATUS_FIXED : BACKPORTED_FIX_STATUS_UNKNOWN;
+    }
+
+    private static boolean isBitSet(List<Long> bitsetLongArray, int bitIndex) {
+        // Because java.util.BitSet is not threadsafe do the calculations here instead.
+        if (bitIndex < 0) {
+            return false;
+        }
+        int arrayIndex = bitIndex >> 6;
+        if (bitsetLongArray.size() <= arrayIndex) {
+            return false;
+        }
+        return (bitsetLongArray.get(arrayIndex) & (1L << bitIndex)) != 0;
     }
 
     /**
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index f728552..bf7116d 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -1371,7 +1371,6 @@
         writeInt(N);
         if (DEBUG_ARRAY_MAP) {
             RuntimeException here =  new RuntimeException("here");
-            here.fillInStackTrace();
             Log.d(TAG, "Writing " + N + " ArrayMap entries", here);
         }
         int startPos;
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 0cffd9f..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;
 
@@ -1483,6 +1484,15 @@
         public @interface PrimitiveType {
         }
 
+        /** @hide */
+        @IntDef(prefix = { "DELAY_TYPE_" }, value = {
+                DELAY_TYPE_PAUSE,
+                DELAY_TYPE_RELATIVE_START_OFFSET,
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface DelayType {
+        }
+
         /**
          * Exception thrown when adding an element to a {@link Composition} that already ends in an
          * indefinitely repeating effect.
@@ -1541,6 +1551,53 @@
         // Internally this maps to the HAL constant CompositePrimitive::LOW_TICK
         public static final int PRIMITIVE_LOW_TICK = 8;
 
+        /**
+         * The delay represents a pause in the composition between the end of the previous primitive
+         * and the beginning of the next one.
+         *
+         * <p>The primitive will start after the requested pause after the last primitive ended.
+         * The actual time the primitive will be played depends on the previous primitive's actual
+         * duration on the device hardware. This enables the combination of primitives to create
+         * more complex effects based on how close to each other they'll play. Here is an example:
+         *
+         * <pre>
+         *     VibrationEffect popEffect = VibrationEffect.startComposition()
+         *         .addPrimitive(PRIMITIVE_QUICK_RISE)
+         *         .addPrimitive(PRIMITIVE_CLICK, 0.7, 50, DELAY_TYPE_PAUSE)
+         *         .compose()
+         * </pre>
+         */
+        @FlaggedApi(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
+        public static final int DELAY_TYPE_PAUSE = 0;
+
+        /**
+         * The delay represents an offset before starting this primitive, relative to the start
+         * time of the previous primitive in the composition.
+         *
+         * <p>The primitive will start at the requested fixed time after the last primitive started,
+         * independently of that primitive's actual duration on the device hardware. This enables
+         * precise timings of primitives within a composition, ensuring they'll be played at the
+         * desired intervals. Here is an example:
+         *
+         * <pre>
+         *     VibrationEffect.startComposition()
+         *         .addPrimitive(PRIMITIVE_CLICK, 1.0)
+         *         .addPrimitive(PRIMITIVE_TICK, 1.0, 20, DELAY_TYPE_RELATIVE_START_OFFSET)
+         *         .addPrimitive(PRIMITIVE_THUD, 1.0, 80, DELAY_TYPE_RELATIVE_START_OFFSET)
+         *         .compose()
+         * </pre>
+         *
+         * Will be performed on the device as follows:
+         *
+         * <pre>
+         *  0ms               20ms                     100ms
+         *  PRIMITIVE_CLICK---PRIMITIVE_TICK-----------PRIMITIVE_THUD
+         * </pre>
+         *
+         * <p>A primitive will be dropped from the composition if it overlaps with previous ones.
+         */
+        @FlaggedApi(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
+        public static final int DELAY_TYPE_RELATIVE_START_OFFSET = 1;
 
         private final ArrayList<VibrationEffectSegment> mSegments = new ArrayList<>();
         private int mRepeatIndex = -1;
@@ -1665,7 +1722,26 @@
         @NonNull
         public Composition addPrimitive(@PrimitiveType int primitiveId,
                 @FloatRange(from = 0f, to = 1f) float scale, @IntRange(from = 0) int delay) {
-            PrimitiveSegment primitive = new PrimitiveSegment(primitiveId, scale, delay);
+            return addPrimitive(primitiveId, scale, delay, PrimitiveSegment.DEFAULT_DELAY_TYPE);
+        }
+
+        /**
+         * Add a haptic primitive to the end of the current composition.
+         *
+         * @param primitiveId The primitive to add
+         * @param scale The scale to apply to the intensity of the primitive.
+         * @param delay The amount of time in milliseconds to wait before playing this primitive,
+         *              as defined by the given {@code delayType}.
+         * @param delayType The type of delay to be applied, e.g. a pause between last primitive and
+         *                  this one or a start offset.
+         * @return This {@link Composition} object to enable adding multiple elements in one chain.
+         */
+        @FlaggedApi(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
+        @NonNull
+        public Composition addPrimitive(@PrimitiveType int primitiveId,
+                @FloatRange(from = 0f, to = 1f) float scale, @IntRange(from = 0) int delay,
+                @DelayType int delayType) {
+            PrimitiveSegment primitive = new PrimitiveSegment(primitiveId, scale, delay, delayType);
             primitive.validate();
             return addSegment(primitive);
         }
@@ -1733,6 +1809,20 @@
                 default -> Integer.toString(id);
             };
         }
+
+        /**
+         * Convert the delay type to a human readable string for debugging.
+         * @param type The delay type to convert
+         * @return The delay type in a human readable format.
+         * @hide
+         */
+        public static String delayTypeToString(@DelayType int type) {
+            return switch (type) {
+                case DELAY_TYPE_PAUSE -> "PAUSE";
+                case DELAY_TYPE_RELATIVE_START_OFFSET -> "START_OFFSET";
+                default -> Integer.toString(type);
+            };
+        }
     }
 
     /**
@@ -1819,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/vibrator/PrimitiveSegment.java b/core/java/android/os/vibrator/PrimitiveSegment.java
index 91653ed..889d735 100644
--- a/core/java/android/os/vibrator/PrimitiveSegment.java
+++ b/core/java/android/os/vibrator/PrimitiveSegment.java
@@ -26,6 +26,7 @@
 
 import com.android.internal.util.Preconditions;
 
+import java.util.Locale;
 import java.util.Objects;
 
 /**
@@ -43,19 +44,29 @@
     /** @hide */
     public static final int DEFAULT_DELAY_MILLIS = 0;
 
+    /** @hide */
+    public static final int DEFAULT_DELAY_TYPE = VibrationEffect.Composition.DELAY_TYPE_PAUSE;
+
     private final int mPrimitiveId;
     private final float mScale;
     private final int mDelay;
+    private final int mDelayType;
 
     PrimitiveSegment(@NonNull Parcel in) {
-        this(in.readInt(), in.readFloat(), in.readInt());
+        this(in.readInt(), in.readFloat(), in.readInt(), in.readInt());
     }
 
     /** @hide */
     public PrimitiveSegment(int id, float scale, int delay) {
+        this(id, scale, delay, DEFAULT_DELAY_TYPE);
+    }
+
+    /** @hide */
+    public PrimitiveSegment(int id, float scale, int delay, int delayType) {
         mPrimitiveId = id;
         mScale = scale;
         mDelay = delay;
+        mDelayType = delayType;
     }
 
     public int getPrimitiveId() {
@@ -70,6 +81,11 @@
         return mDelay;
     }
 
+    /** @hide */
+    public int getDelayType() {
+        return mDelayType;
+    }
+
     @Override
     public long getDuration() {
         return -1;
@@ -112,8 +128,7 @@
         if (Float.compare(mScale, newScale) == 0) {
             return this;
         }
-
-        return new PrimitiveSegment(mPrimitiveId, newScale, mDelay);
+        return new PrimitiveSegment(mPrimitiveId, newScale, mDelay, mDelayType);
     }
 
     /** @hide */
@@ -124,8 +139,7 @@
         if (Float.compare(mScale, newScale) == 0) {
             return this;
         }
-
-        return new PrimitiveSegment(mPrimitiveId, newScale, mDelay);
+        return new PrimitiveSegment(mPrimitiveId, newScale, mDelay, mDelayType);
     }
 
     /** @hide */
@@ -142,6 +156,7 @@
                 VibrationEffect.Composition.PRIMITIVE_LOW_TICK, "primitiveId");
         Preconditions.checkArgumentInRange(mScale, 0f, 1f, "scale");
         VibrationEffectSegment.checkDurationArgument(mDelay, "delay");
+        Preconditions.checkArgument(isValidDelayType(mDelayType), "delayType");
     }
 
     @Override
@@ -150,6 +165,7 @@
         dest.writeInt(mPrimitiveId);
         dest.writeFloat(mScale);
         dest.writeInt(mDelay);
+        dest.writeInt(mDelayType);
     }
 
     @Override
@@ -163,14 +179,16 @@
                 + "primitive=" + VibrationEffect.Composition.primitiveToString(mPrimitiveId)
                 + ", scale=" + mScale
                 + ", delay=" + mDelay
+                + ", delayType=" + VibrationEffect.Composition.delayTypeToString(mDelayType)
                 + '}';
     }
 
     /** @hide */
     @Override
     public String toDebugString() {
-        return String.format("Primitive=%s(scale=%.2f, delay=%dms)",
-                VibrationEffect.Composition.primitiveToString(mPrimitiveId), mScale, mDelay);
+        return String.format(Locale.ROOT, "Primitive=%s(scale=%.2f, %s=%dms)",
+                VibrationEffect.Composition.primitiveToString(mPrimitiveId), mScale,
+                toDelayTypeDebugString(mDelayType), mDelay);
     }
 
     @Override
@@ -180,12 +198,28 @@
         PrimitiveSegment that = (PrimitiveSegment) o;
         return mPrimitiveId == that.mPrimitiveId
                 && Float.compare(that.mScale, mScale) == 0
-                && mDelay == that.mDelay;
+                && mDelay == that.mDelay
+                && mDelayType == that.mDelayType;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mPrimitiveId, mScale, mDelay);
+        return Objects.hash(mPrimitiveId, mScale, mDelay, mDelayType);
+    }
+
+    private static boolean isValidDelayType(int delayType) {
+        return switch (delayType) {
+            case VibrationEffect.Composition.DELAY_TYPE_PAUSE,
+                 VibrationEffect.Composition.DELAY_TYPE_RELATIVE_START_OFFSET -> true;
+            default -> false;
+        };
+    }
+
+    private static String toDelayTypeDebugString(int delayType) {
+        return switch (delayType) {
+            case VibrationEffect.Composition.DELAY_TYPE_RELATIVE_START_OFFSET -> "startOffset";
+            default -> "pause";
+        };
     }
 
     @NonNull
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/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 33040be..6264fbb 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -391,3 +391,12 @@
     description: "Batch noteOperations on the client to reduce binder call volume"
     bug: "366013082"
 }
+
+flag {
+    name: "supervision_role_permission_update_enabled"
+    is_fixed_read_only: true
+    is_exported: true
+    namespace: "supervision"
+    description: "This flag is used to enable all the remaining permissions required to the supervision role"
+    bug: "367333883"
+}
\ No newline at end of file
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/notification/INotificationListener.aidl b/core/java/android/service/notification/INotificationListener.aidl
index b384b66..5471048 100644
--- a/core/java/android/service/notification/INotificationListener.aidl
+++ b/core/java/android/service/notification/INotificationListener.aidl
@@ -34,10 +34,14 @@
     void onListenerConnected(in NotificationRankingUpdate update);
     void onNotificationPosted(in IStatusBarNotificationHolder notificationHolder,
             in NotificationRankingUpdate update);
+    void onNotificationPostedFull(in StatusBarNotification sbn,
+            in NotificationRankingUpdate update);
     void onStatusBarIconsBehaviorChanged(boolean hideSilentStatusIcons);
     // stats only for assistant
     void onNotificationRemoved(in IStatusBarNotificationHolder notificationHolder,
             in NotificationRankingUpdate update, in NotificationStats stats, int reason);
+    void onNotificationRemovedFull(in StatusBarNotification sbn,
+                in NotificationRankingUpdate update, in NotificationStats stats, int reason);
     void onNotificationRankingUpdate(in NotificationRankingUpdate update);
     void onListenerHintsChanged(int hints);
     void onInterruptionFilterChanged(int interruptionFilter);
@@ -48,7 +52,9 @@
 
     // assistants only
     void onNotificationEnqueuedWithChannel(in IStatusBarNotificationHolder notificationHolder, in NotificationChannel channel, in NotificationRankingUpdate update);
+    void onNotificationEnqueuedWithChannelFull(in StatusBarNotification sbn, in NotificationChannel channel, in NotificationRankingUpdate update);
     void onNotificationSnoozedUntilContext(in IStatusBarNotificationHolder notificationHolder, String snoozeCriterionId);
+    void onNotificationSnoozedUntilContextFull(in StatusBarNotification sbn, String snoozeCriterionId);
     void onNotificationsSeen(in List<String> keys);
     void onPanelRevealed(int items);
     void onPanelHidden();
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index 091b25a..0a9276c 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -423,7 +423,12 @@
                         + "Error receiving StatusBarNotification");
                 return;
             }
+            onNotificationEnqueuedWithChannelFull(sbn, channel, update);
+        }
 
+        @Override
+        public void onNotificationEnqueuedWithChannelFull(StatusBarNotification sbn,
+                NotificationChannel channel, NotificationRankingUpdate update) {
             applyUpdateLocked(update);
             SomeArgs args = SomeArgs.obtain();
             args.arg1 = sbn;
@@ -447,7 +452,12 @@
                 Log.w(TAG, "onNotificationSnoozed: Error receiving StatusBarNotification");
                 return;
             }
+            onNotificationSnoozedUntilContextFull(sbn, snoozeCriterionId);
+        }
 
+        @Override
+        public void onNotificationSnoozedUntilContextFull(
+                StatusBarNotification sbn, String snoozeCriterionId) {
             SomeArgs args = SomeArgs.obtain();
             args.arg1 = sbn;
             args.arg2 = snoozeCriterionId;
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index a8ab211..5d0ec73 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1490,7 +1490,12 @@
                 Log.w(TAG, "onNotificationPosted: Error receiving StatusBarNotification");
                 return;
             }
+            onNotificationPostedFull(sbn, update);
+        }
 
+        @Override
+        public void onNotificationPostedFull(StatusBarNotification sbn,
+                NotificationRankingUpdate update) {
             try {
                 // convert icon metadata to legacy format for older clients
                 createLegacyIconExtras(sbn.getNotification());
@@ -1518,7 +1523,6 @@
                             mRankingMap).sendToTarget();
                 }
             }
-
         }
 
         @Override
@@ -1531,6 +1535,12 @@
                 Log.w(TAG, "onNotificationRemoved: Error receiving StatusBarNotification", e);
                 return;
             }
+            onNotificationRemovedFull(sbn, update, stats, reason);
+        }
+
+        @Override
+        public void onNotificationRemovedFull(StatusBarNotification sbn,
+                NotificationRankingUpdate update, NotificationStats stats, int reason) {
             if (sbn == null) {
                 Log.w(TAG, "onNotificationRemoved: Error receiving StatusBarNotification");
                 return;
@@ -1592,6 +1602,14 @@
         }
 
         @Override
+        public void onNotificationEnqueuedWithChannelFull(
+                StatusBarNotification sbn, NotificationChannel channel,
+                NotificationRankingUpdate update)
+                throws RemoteException {
+            // no-op in the listener
+        }
+
+        @Override
         public void onNotificationsSeen(List<String> keys)
                 throws RemoteException {
             // no-op in the listener
@@ -1621,6 +1639,13 @@
         }
 
         @Override
+        public void onNotificationSnoozedUntilContextFull(
+                StatusBarNotification sbn, String snoozeCriterionId)
+                throws RemoteException {
+            // no-op in the listener
+        }
+
+        @Override
         public void onNotificationExpansionChanged(
                 String key, boolean isUserAction, boolean isExpanded) {
             // no-op in the listener
@@ -1688,8 +1713,6 @@
                 Bundle feedback) {
             // no-op in the listener
         }
-
-
     }
 
     /**
diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java
index ae12132..a42eece 100644
--- a/core/java/android/text/Html.java
+++ b/core/java/android/text/Html.java
@@ -614,7 +614,7 @@
                 if (style[j] instanceof TypefaceSpan) {
                     String s = ((TypefaceSpan) style[j]).getFamily();
 
-                    if (s.equals("monospace")) {
+                    if ("monospace".equals(s)) {
                         out.append("</tt>");
                     }
                 }
diff --git a/core/java/android/util/ArrayMap.java b/core/java/android/util/ArrayMap.java
index 174e0c8..7ee0ff1 100644
--- a/core/java/android/util/ArrayMap.java
+++ b/core/java/android/util/ArrayMap.java
@@ -649,7 +649,6 @@
         }
         if (index > 0 && mHashes[index-1] > hash) {
             RuntimeException e = new RuntimeException("here");
-            e.fillInStackTrace();
             Log.w(TAG, "New hash " + hash
                     + " is before end of array hash " + mHashes[index-1]
                     + " at index " + index + (DEBUG ? " key " + key : ""), e);
diff --git a/core/java/android/util/ArraySet.java b/core/java/android/util/ArraySet.java
index bfbca07..1344bb9 100644
--- a/core/java/android/util/ArraySet.java
+++ b/core/java/android/util/ArraySet.java
@@ -526,7 +526,6 @@
             // Cannot optimize since it would break the sorted order - fallback to add()
             if (DEBUG) {
                 RuntimeException e = new RuntimeException("here");
-                e.fillInStackTrace();
                 Log.w(TAG, "New hash " + hash
                         + " is before end of array hash " + mHashes[index - 1]
                         + " at index " + index, e);
diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java
index 5406cf5..264db4a 100644
--- a/core/java/android/view/AttachedSurfaceControl.java
+++ b/core/java/android/view/AttachedSurfaceControl.java
@@ -15,9 +15,11 @@
  */
 package android.view;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.UiThread;
 import android.content.Context;
 import android.graphics.Rect;
@@ -29,6 +31,8 @@
 
 import com.android.window.flags.Flags;
 
+import java.util.concurrent.Executor;
+
 /**
  * Provides an interface to the root-Surface of a View Hierarchy or Window. This
  * is used in combination with the {@link android.view.SurfaceControl} API to enable
@@ -202,4 +206,21 @@
         throw new UnsupportedOperationException("The getInputTransferToken needs to be "
                 + "implemented before making this call.");
     }
+
+    /**
+     * Registers a {@link OnJankDataListener} to receive jank classification data about rendered
+     * frames.
+     *
+     * @param executor The executor on which the listener will be invoked.
+     * @param listener The listener to add.
+     * @return The {@link OnJankDataListenerRegistration} for the listener.
+     */
+    @NonNull
+    @FlaggedApi(Flags.FLAG_JANK_API)
+    @SuppressLint("PairedRegistration")
+    default SurfaceControl.OnJankDataListenerRegistration registerOnJankDataListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull SurfaceControl.OnJankDataListener listener) {
+        return SurfaceControl.OnJankDataListenerRegistration.NONE;
+    }
 }
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 7e24749..5a71282 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -189,6 +189,11 @@
     @UnsupportedAppUsage
     private long mLastFrameTimeNanos;
 
+    // Keeps track of the last scheduled frame time without additional offsets
+    // added from buffer stuffing recovery. Used to compare timing of vsyncs to
+    // determine idle state.
+    private long mLastNoOffsetFrameTimeNanos;
+
     /** DO NOT USE since this will not updated when screen refresh changes. */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R,
             publicAlternatives = "Use {@link android.view.Display#getRefreshRate} instead")
@@ -203,6 +208,50 @@
     private final FrameData mFrameData = new FrameData();
     private volatile boolean mInDoFrameCallback = false;
 
+    private static class BufferStuffingData {
+        enum RecoveryAction {
+            // No recovery
+            NONE,
+            // Recovery has started, adds a negative offset
+            OFFSET,
+            // Recovery has started, delays a frame to return buffer count
+            // back toward threshold.
+            DELAY_FRAME
+        }
+        // The maximum number of times frames will be delayed per buffer stuffing event.
+        // Since buffer stuffing can persist for several consecutive frames following the
+        // initial missed frame, we want to adjust the timeline with enough frame delays and
+        // offsets to return the queued buffer count back to threshold.
+        public static final int MAX_FRAME_DELAYS = 3;
+
+        // Whether buffer stuffing recovery has begun. Recovery can only end
+        // when events are idle.
+        public boolean isRecovering = false;
+
+        // The number of frames delayed so far during recovery. Used to compare with
+        // MAX_FRAME_DELAYS to safeguard against excessive frame delays during recovery.
+        // Also used as unique cookie for tracing.
+        public int numberFrameDelays = 0;
+
+        // The number of additional frame delays scheduled during recovery to wait for the next
+        // vsync. These are scheduled when frame times appear to go backward or frames are
+        // being skipped due to FPSDivisor.
+        public int numberWaitsForNextVsync = 0;
+
+        /**
+         * After buffer stuffing recovery has ended with a detected idle state, the
+         * recovery data trackers can be reset in preparation for any future
+         * stuffing events.
+         */
+        public void reset() {
+            isRecovering = false;
+            numberFrameDelays = 0;
+            numberWaitsForNextVsync = 0;
+        }
+    }
+
+    private final BufferStuffingData mBufferStuffingData = new BufferStuffingData();
+
     /**
      * Contains information about the current frame for jank-tracking,
      * mainly timings of key events along with a bit of metadata about
@@ -850,13 +899,99 @@
         Trace.traceEnd(Trace.TRACE_TAG_VIEW);
     }
 
+    // Conducts logic for beginning or ending buffer stuffing recovery.
+    // Returns an enum for the recovery action that should be taken in doFrame().
+    BufferStuffingData.RecoveryAction checkBufferStuffingRecovery(long frameTimeNanos,
+            DisplayEventReceiver.VsyncEventData vsyncEventData) {
+        // Canned animations can recover from buffer stuffing whenever more
+        // than 2 buffers are queued.
+        if (vsyncEventData.numberQueuedBuffers > 2) {
+            mBufferStuffingData.isRecovering = true;
+            // Intentional frame delay that can happen at most MAX_FRAME_DELAYS times per
+            // buffer stuffing event until the buffer count returns to threshold. The
+            // delayed frames are compensated for by the negative offsets added to the
+            // animation timestamps.
+            if (mBufferStuffingData.numberFrameDelays < mBufferStuffingData.MAX_FRAME_DELAYS) {
+                if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+                    Trace.asyncTraceForTrackBegin(
+                            Trace.TRACE_TAG_VIEW, "Buffer stuffing recovery", "Thread "
+                            + android.os.Process.myTid() + ", recover frame #"
+                            + mBufferStuffingData.numberFrameDelays,
+                            mBufferStuffingData.numberFrameDelays);
+                }
+                mBufferStuffingData.numberFrameDelays++;
+                scheduleVsyncLocked();
+                return BufferStuffingData.RecoveryAction.DELAY_FRAME;
+            }
+        }
+
+        if (mBufferStuffingData.isRecovering) {
+            // Includes an additional expected frame delay from the natural scheduling
+            // of the next vsync event.
+            int totalFrameDelays = mBufferStuffingData.numberFrameDelays
+                    + mBufferStuffingData.numberWaitsForNextVsync + 1;
+            long vsyncsSinceLastCallback =
+                    (frameTimeNanos - mLastNoOffsetFrameTimeNanos) / mLastFrameIntervalNanos;
+
+            // Detected idle state due to a longer inactive period since the last vsync callback
+            // than the total expected number of vsync frame delays. End buffer stuffing recovery.
+            // There are no frames to animate and offsets no longer need to be added
+            // since the idle state gives the animation a chance to catch up.
+            if (vsyncsSinceLastCallback > totalFrameDelays) {
+                if (DEBUG_JANK) {
+                    Log.d(TAG, "End buffer stuffing recovery");
+                }
+                if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+                    for (int i = 0; i < mBufferStuffingData.numberFrameDelays; i++) {
+                        Trace.asyncTraceForTrackEnd(
+                                Trace.TRACE_TAG_VIEW, "Buffer stuffing recovery", i);
+                    }
+                }
+                mBufferStuffingData.reset();
+
+            } else {
+                if (DEBUG_JANK) {
+                    Log.d(TAG, "Adjust animation timeline with a negative offset");
+                }
+                if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+                    Trace.instantForTrack(
+                            Trace.TRACE_TAG_VIEW, "Buffer stuffing recovery",
+                            "Negative offset added to animation");
+                }
+                return BufferStuffingData.RecoveryAction.OFFSET;
+            }
+        }
+        return BufferStuffingData.RecoveryAction.NONE;
+    }
+
     void doFrame(long frameTimeNanos, int frame,
             DisplayEventReceiver.VsyncEventData vsyncEventData) {
         final long startNanos;
         final long frameIntervalNanos = vsyncEventData.frameInterval;
         boolean resynced = false;
+        long offsetFrameTimeNanos = frameTimeNanos;
+
+        // Evaluate if buffer stuffing recovery needs to start or end, and
+        // what actions need to be taken for recovery.
+        switch (checkBufferStuffingRecovery(frameTimeNanos, vsyncEventData)) {
+            case NONE:
+                // Without buffer stuffing recovery, offsetFrameTimeNanos is
+                // synonymous with frameTimeNanos.
+                break;
+            case OFFSET:
+                // Add animation offset. Used to update frame timeline with
+                // offset before jitter is calculated.
+                offsetFrameTimeNanos = frameTimeNanos - frameIntervalNanos;
+                break;
+            case DELAY_FRAME:
+                // Intentional frame delay to help restore queued buffer count to threshold.
+                return;
+            default:
+                break;
+        }
+
         try {
-            FrameTimeline timeline = mFrameData.update(frameTimeNanos, vsyncEventData);
+            FrameTimeline timeline = mFrameData.update(offsetFrameTimeNanos, vsyncEventData);
             if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
                 Trace.traceBegin(
                         Trace.TRACE_TAG_VIEW, "Choreographer#doFrame " + timeline.mVsyncId);
@@ -867,15 +1002,18 @@
                     traceMessage("Frame not scheduled");
                     return; // no work to do
                 }
+                mLastNoOffsetFrameTimeNanos = frameTimeNanos;
 
                 if (DEBUG_JANK && mDebugPrintNextFrameTimeDelta) {
                     mDebugPrintNextFrameTimeDelta = false;
                     Log.d(TAG, "Frame time delta: "
-                            + ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms");
+                            + ((offsetFrameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms");
                 }
 
-                long intendedFrameTimeNanos = frameTimeNanos;
+                long intendedFrameTimeNanos = offsetFrameTimeNanos;
                 startNanos = System.nanoTime();
+                // Calculating jitter involves using the original frame time without
+                // adjustments from buffer stuffing
                 final long jitterNanos = startNanos - frameTimeNanos;
                 if (jitterNanos >= frameIntervalNanos) {
                     frameTimeNanos = startNanos;
@@ -899,6 +1037,13 @@
                                     + " ms in the past.");
                         }
                     }
+                    if (mBufferStuffingData.isRecovering) {
+                        frameTimeNanos -= frameIntervalNanos;
+                        if (DEBUG_JANK) {
+                            Log.d(TAG, "Adjusted animation timeline with a negative offset after"
+                                    + " jitter calculation");
+                        }
+                    }
                     timeline = mFrameData.update(
                             frameTimeNanos, mDisplayEventReceiver, jitterNanos);
                     resynced = true;
@@ -910,6 +1055,9 @@
                                 + "previously skipped frame.  Waiting for next vsync.");
                     }
                     traceMessage("Frame time goes backward");
+                    if (mBufferStuffingData.isRecovering) {
+                        mBufferStuffingData.numberWaitsForNextVsync++;
+                    }
                     scheduleVsyncLocked();
                     return;
                 }
@@ -918,6 +1066,9 @@
                     long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
                     if (timeSinceVsync < (frameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
                         traceMessage("Frame skipped due to FPSDivisor");
+                        if (mBufferStuffingData.isRecovering) {
+                            mBufferStuffingData.numberWaitsForNextVsync++;
+                        }
                         scheduleVsyncLocked();
                         return;
                     }
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index fc7a65d..bb233d2 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -207,6 +207,8 @@
         // reasonable timestamps.
         public int frameTimelinesLength = 1;
 
+        public int numberQueuedBuffers = 0;
+
         VsyncEventData() {
             frameTimelines = new FrameTimeline[FRAME_TIMELINES_CAPACITY];
             for (int i = 0; i < frameTimelines.length; i++) {
@@ -217,11 +219,13 @@
         // Called from native code.
         @SuppressWarnings("unused")
         VsyncEventData(FrameTimeline[] frameTimelines, int preferredFrameTimelineIndex,
-                int frameTimelinesLength, long frameInterval) {
+                int frameTimelinesLength, long frameInterval,
+                int numberQueuedBuffers) {
             this.frameTimelines = frameTimelines;
             this.preferredFrameTimelineIndex = preferredFrameTimelineIndex;
             this.frameTimelinesLength = frameTimelinesLength;
             this.frameInterval = frameInterval;
+            this.numberQueuedBuffers = numberQueuedBuffers;
         }
 
         void copyFrom(VsyncEventData other) {
@@ -231,6 +235,7 @@
             for (int i = 0; i < frameTimelines.length; i++) {
                 frameTimelines[i].copyFrom(other.frameTimelines[i]);
             }
+            numberQueuedBuffers = other.numberQueuedBuffers;
         }
 
         public FrameTimeline preferredFrameTimeline() {
diff --git a/core/java/android/view/FrameMetrics.java b/core/java/android/view/FrameMetrics.java
index 9e25a3e..58b2a67 100644
--- a/core/java/android/view/FrameMetrics.java
+++ b/core/java/android/view/FrameMetrics.java
@@ -18,10 +18,13 @@
 
 import static android.graphics.FrameInfo.FLAG_WINDOW_VISIBILITY_CHANGED;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 
+import com.android.window.flags.Flags;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
@@ -177,6 +180,16 @@
     public static final int DEADLINE = 13;
 
     /**
+     * Metric identifier for the frame's VSync identifier.
+     * <p>
+     * The id that corresponds to the chosen frame timeline, used to correlate a frame produced
+     * by HWUI with the timeline data from the compositor.
+     * </p>
+     */
+    @FlaggedApi(Flags.FLAG_JANK_API)
+    public static final int FRAME_TIMELINE_VSYNC_ID = 14;
+
+    /**
      * Identifiers for metrics available for each frame.
      *
      * {@see #getMetric(int)}
@@ -337,7 +350,8 @@
      * @return the value of the metric or -1 if it is not available.
      */
     public long getMetric(@Metric int id) {
-        if (id < UNKNOWN_DELAY_DURATION || id > DEADLINE) {
+        if (id < UNKNOWN_DELAY_DURATION
+                || id > (Flags.jankApi() ? FRAME_TIMELINE_VSYNC_ID : DEADLINE)) {
             return -1;
         }
 
@@ -351,6 +365,8 @@
             return mTimingData[Index.INTENDED_VSYNC];
         } else if (id == VSYNC_TIMESTAMP) {
             return mTimingData[Index.VSYNC];
+        } else if (id == FRAME_TIMELINE_VSYNC_ID) {
+            return mTimingData[Index.FRAME_TIMELINE_VSYNC_ID];
         }
 
         int durationsIdx = 2 * id;
@@ -358,4 +374,3 @@
                 - mTimingData[DURATIONS[durationsIdx]];
     }
 }
-
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 206c737..68efa79 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -22,6 +22,7 @@
 import static android.graphics.Matrix.MSKEW_Y;
 import static android.graphics.Matrix.MTRANS_X;
 import static android.graphics.Matrix.MTRANS_Y;
+import static android.view.flags.Flags.bufferStuffingRecovery;
 import static android.view.SurfaceControlProto.HASH_CODE;
 import static android.view.SurfaceControlProto.LAYER_ID;
 import static android.view.SurfaceControlProto.NAME;
@@ -410,8 +411,19 @@
 
     /**
      * Jank information to be fed back via {@link OnJankDataListener}.
-     * @hide
+     * <p>
+     * Apps may register a {@link OnJankDataListener} to get periodic batches of jank classification
+     * data from the (<a
+     * href="https://source.android.com/docs/core/graphics/surfaceflinger-windowmanagersystem">
+     * composer</a> regarding rendered frames. A frame is considered janky if it did not reach the
+     * display at the intended time, typically due to missing a rendering deadline. This API
+     * provides information that can be used to identify the root cause of the scheduling misses
+     * and provides overall frame scheduling statistics.
+     * <p>
+     * This API can be used in conjunction with the {@link FrameMetrics} API by associating jank
+     * classification data with {@link FrameMetrics} data via the frame VSync id.
      */
+    @FlaggedApi(Flags.FLAG_JANK_API)
     public static class JankData {
 
         /**
@@ -428,29 +440,105 @@
         @Retention(RetentionPolicy.SOURCE)
         public @interface JankType {}
 
-        // No Jank
+        /**
+         * No jank detected, the frame was on time.
+         */
         public static final int JANK_NONE = 0;
-        // Jank caused by the composer missing a deadline
+
+        /**
+         * Bitmask for jank due to deadlines missed by the composer.
+         */
         public static final int JANK_COMPOSER = 1 << 0;
-        // Jank caused by the application missing the composer's deadline
+
+        /**
+         * Bitmask for jank due to deadlines missed by the application.
+         */
         public static final int JANK_APPLICATION = 1 << 1;
-        // Jank due to other unknown reasons
+
+        /**
+         * Bitmask for jank due to deadlines missed by other system components.
+         */
         public static final int JANK_OTHER = 1 << 2;
 
+        private final long mFrameVsyncId;
+        private final @JankType int mJankType;
+        private final long mFrameIntervalNs;
+        private final long mScheduledAppFrameTimeNs;
+        private final long mActualAppFrameTimeNs;
+
+        /**
+         * @hide
+         */
         public JankData(long frameVsyncId, @JankType int jankType, long frameIntervalNs,
                 long scheduledAppFrameTimeNs, long actualAppFrameTimeNs) {
-            this.frameVsyncId = frameVsyncId;
-            this.jankType = jankType;
-            this.frameIntervalNs = frameIntervalNs;
-            this.scheduledAppFrameTimeNs = scheduledAppFrameTimeNs;
-            this.actualAppFrameTimeNs = actualAppFrameTimeNs;
+            mFrameVsyncId = frameVsyncId;
+            mJankType = jankType;
+            mFrameIntervalNs = frameIntervalNs;
+            mScheduledAppFrameTimeNs = scheduledAppFrameTimeNs;
+            mActualAppFrameTimeNs = actualAppFrameTimeNs;
         }
 
-        public final long frameVsyncId;
-        public final @JankType int jankType;
-        public final long frameIntervalNs;
-        public final long scheduledAppFrameTimeNs;
-        public final long actualAppFrameTimeNs;
+        /**
+         * Returns the id of the frame for this jank classification.
+         *
+         * @see FrameMetrics#FRAME_TIMELINE_VSYNC_ID
+         * @see Choreographer.FrameTimeline#getVsyncId
+         * @see Transaction#setFrameTimeline
+         * @return the frame id
+         */
+        public long getVsyncId() {
+            return mFrameVsyncId;
+        }
+
+        /**
+         * Returns the bitmask indicating the types of jank observed.
+         *
+         * @return the jank type bitmask
+         */
+        public @JankType int getJankType() {
+            return mJankType;
+        }
+
+        /**
+         * Returns the time between frame VSyncs in nanoseconds.
+         *
+         * @return the frame interval in ns
+         * @hide
+         */
+        public long getFrameIntervalNanos() {
+            return mFrameIntervalNs;
+        }
+
+        /**
+         * Returns the duration in nanoseconds the application was scheduled to use to render this
+         * frame.
+         * <p>
+         * Note that this may be higher than the frame interval to allow for CPU/GPU
+         * parallelization of work.
+         *
+         * @return scheduled app time in ns
+         */
+        public long getScheduledAppFrameTimeNanos() {
+            return mScheduledAppFrameTimeNs;
+        }
+
+        /**
+         * Returns the actual time in nanoseconds taken by the application to render this frame.
+         *
+         * @return the actual app time in ns
+         */
+        public long getActualAppFrameTimeNanos() {
+            return mActualAppFrameTimeNs;
+        }
+
+        @Override
+        public String toString() {
+            return "JankData{vsync=" + mFrameVsyncId
+                    + ", jankType=0x" + Integer.toHexString(mJankType)
+                    + ", frameInterval=" + mFrameIntervalNs + "ns"
+                    + ", scheduledAppTime=" + mScheduledAppFrameTimeNs + "ns"
+                    + ", actualAppTime=" + mActualAppFrameTimeNs + "ns}";
+        }
     }
 
     /**
@@ -458,12 +546,13 @@
      * surface.
      *
      * @see JankData
-     * @see #addJankDataListener
-     * @hide
+     * @see #addOnJankDataListener
      */
+    @FlaggedApi(Flags.FLAG_JANK_API)
     public interface OnJankDataListener {
         /**
-         * Called when new jank classifications are available.
+         * Called when new jank classifications are available. The listener is invoked out of band
+         * of the rendered frames with jank classification data for a batch of frames.
          */
         void onJankDataAvailable(@NonNull List<JankData> jankData);
 
@@ -471,9 +560,22 @@
 
     /**
      * Handle to a registered {@link OnJankDatalistener}.
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_JANK_API)
     public static class OnJankDataListenerRegistration {
+        /** @hide */
+        public static final OnJankDataListenerRegistration NONE =
+                new OnJankDataListenerRegistration() {
+                    @Override
+                    public void flush() {}
+
+                    @Override
+                    public void removeAfter(long afterVsync) {}
+
+                    @Override
+                    public void release() {}
+                };
+
         private final long mNativeObject;
 
         private static final NativeAllocationRegistry sRegistry =
@@ -484,6 +586,11 @@
         private final Runnable mFreeNativeResources;
         private boolean mRemoved = false;
 
+        private OnJankDataListenerRegistration() {
+            mNativeObject = 0;
+            mFreeNativeResources = () -> {};
+        }
+
         OnJankDataListenerRegistration(SurfaceControl surface, OnJankDataListener listener) {
             mNativeObject = nativeCreateJankDataListenerWrapper(surface.mNativeObject, listener);
             mFreeNativeResources = (mNativeObject == 0) ? () -> {} :
@@ -499,10 +606,17 @@
         }
 
         /**
-         * Request the removal of the registered listener after the VSync with the provided ID. Use
-         * a value <= 0 for afterVsync to remove the listener immediately. The given listener will
-         * not be removed before the given VSync, but may still reveive data for frames past the
-         * provided VSync.
+         * Schedule the removal of the registered listener after the frame with the provided id.
+         * <p>
+         * Because jank classification is only possible after frames have been displayed, the
+         * callbacks are always delayed. To ensure receipt of all jank classification data, an
+         * application can schedule the removal to happen no sooner than after the data for the
+         * frame with the provided id has been provided.
+         * <p>
+         * Use a value &lt;= 0 for afterVsync to remove the listener immediately, ensuring no future
+         * callbacks.
+         *
+         * @param afterVsync the id of the Vsync after which to remove the listener
          */
         public void removeAfter(long afterVsync) {
             mRemoved = true;
@@ -511,6 +625,7 @@
 
         /**
          * Free the native resources associated with the listener registration.
+         * @hide
          */
         public void release() {
             if (!mRemoved) {
@@ -663,6 +778,13 @@
     public static final int CAN_OCCLUDE_PRESENTATION = 0x00001000;
 
     /**
+     * Indicates that the SurfaceControl should recover from buffer stuffing when
+     * possible. This is the case when the SurfaceControl is a ViewRootImpl.
+     * @hide
+     */
+    public static final int RECOVERABLE_FROM_BUFFER_STUFFING = 0x00002000;
+
+    /**
      * Surface creation flag: Creates a surface where color components are interpreted
      * as "non pre-multiplied" by their alpha channel. Of course this flag is
      * meaningless for surfaces without an alpha channel. By default
@@ -4867,6 +4989,23 @@
             nativeSetDesiredPresentTimeNanos(mNativeObject, desiredPresentTimeNanos);
             return this;
         }
+
+        /**
+         * Specifies that the SurfaceControl is a buffer producer that should recover from buffer
+         * stuffing, meaning that the SurfaceControl is a ViewRootImpl.
+         *
+         * @hide
+         */
+        @NonNull
+        public Transaction setRecoverableFromBufferStuffing(@NonNull SurfaceControl sc) {
+            if (bufferStuffingRecovery()) {
+                checkPreconditions(sc);
+                nativeSetFlags(mNativeObject, sc.mNativeObject, RECOVERABLE_FROM_BUFFER_STUFFING,
+                        RECOVERABLE_FROM_BUFFER_STUFFING);
+            }
+            return this;
+        }
+
         /**
          * Writes the transaction to parcel, clearing the transaction as if it had been applied so
          * it can be used to store future transactions. It's the responsibility of the parcel
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 75d2da1..e50662a 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -140,6 +140,8 @@
 import android.animation.AnimationHandler;
 import android.animation.LayoutTransition;
 import android.annotation.AnyThread;
+import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.Size;
@@ -1250,7 +1252,6 @@
         mExtraDisplayListenerLogging = !TextUtils.isEmpty(name) && name.equals(mBasePackageName);
         mThread = Thread.currentThread();
         mLocation = new WindowLeaked(null);
-        mLocation.fillInStackTrace();
         mWidth = -1;
         mHeight = -1;
         mDirty = new Rect();
@@ -2758,6 +2759,9 @@
         // Only call transferFrom if the surface has changed to prevent inc the generation ID and
         // causing EGL resources to be recreated.
         mSurface.transferFrom(blastSurface);
+
+        // Since the SurfaceControl is a VRI, indicate that it can recover from buffer stuffing.
+        mTransaction.setRecoverableFromBufferStuffing(mSurfaceControl).applyAsyncUnsafe();
     }
 
     private void setBoundsLayerCrop(Transaction t) {
@@ -11897,6 +11901,20 @@
     }
 
     /**
+     * {@inheritDoc}
+     */
+    @NonNull
+    @Override
+    @FlaggedApi(com.android.window.flags.Flags.FLAG_JANK_API)
+    public SurfaceControl.OnJankDataListenerRegistration registerOnJankDataListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull SurfaceControl.OnJankDataListener listener) {
+        SurfaceControl.OnJankDataListener wrapped = (data) ->
+                executor.execute(() -> listener.onJankDataAvailable(data));
+        return mSurfaceControl.addOnJankDataListener(wrapped);
+    }
+
+    /**
      * Class for managing the accessibility interaction connection
      * based on the global accessibility state.
      */
diff --git a/core/java/android/view/flags/view_flags.aconfig b/core/java/android/view/flags/view_flags.aconfig
index 1b86f96..3b6343e 100644
--- a/core/java/android/view/flags/view_flags.aconfig
+++ b/core/java/android/view/flags/view_flags.aconfig
@@ -132,4 +132,12 @@
     description: "Use refactored round scrollbar."
     bug: "333417898"
     is_fixed_read_only: true
+}
+
+flag {
+    name: "buffer_stuffing_recovery"
+    namespace: "window_surfaces"
+    description: "Recover from buffer stuffing when SurfaceFlinger misses a frame"
+    bug: "294922229"
+    is_fixed_read_only: true
 }
\ No newline at end of file
diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig
index 392c307..96b9dc7 100644
--- a/core/java/android/window/flags/window_surfaces.aconfig
+++ b/core/java/android/window/flags/window_surfaces.aconfig
@@ -97,3 +97,12 @@
     is_fixed_read_only: true
     bug: "308662081"
 }
+
+flag {
+    name: "jank_api"
+    namespace: "window_surfaces"
+    description: "Adds the jank data listener to AttachedSurfaceControl"
+    is_fixed_read_only: true
+    is_exported: true
+    bug: "293949943"
+}
diff --git a/core/java/com/android/internal/app/procstats/AssociationState.java b/core/java/com/android/internal/app/procstats/AssociationState.java
index a21a842..543adac 100644
--- a/core/java/com/android/internal/app/procstats/AssociationState.java
+++ b/core/java/com/android/internal/app/procstats/AssociationState.java
@@ -257,7 +257,6 @@
                         if (VALIDATE_TIMES) {
                             if (mActiveDuration > mAssociationState.mTotalActiveDuration) {
                                 RuntimeException ex = new RuntimeException();
-                                ex.fillInStackTrace();
                                 Slog.w(TAG, "Source act duration " + mActiveDurations
                                         + " exceeds total " + mAssociationState.mTotalActiveDuration
                                         + " in procstate " + mActiveProcState + " in source "
@@ -650,7 +649,6 @@
                         + mySrc.mKey.mProcess + " to assoc " + mName);
                 if ((mySrc.mDuration + otherSrc.mDuration) > mTotalDuration) {
                     RuntimeException ex = new RuntimeException();
-                    ex.fillInStackTrace();
                     Slog.w(TAG, "Source tot duration " + mySrc.mDuration + "+"
                             + otherSrc.mDuration
                             + (newSrc ? " (new)" : " (old)") + " exceeds total "
@@ -665,7 +663,6 @@
                             + mySrc.mKey.mProcess + " to assoc " + mName);
                     if ((mySrc.mActiveDuration + otherSrc.mActiveDuration) > mTotalDuration) {
                         RuntimeException ex = new RuntimeException();
-                        ex.fillInStackTrace();
                         Slog.w(TAG, "Source act duration " + mySrc.mActiveDuration + "+"
                                 + otherSrc.mActiveDuration
                                 + (newSrc ? " (new)" : " (old)") + " exceeds total "
@@ -746,14 +743,12 @@
             if (VALIDATE_TIMES) {
                 if (src.mDuration > mTotalDuration) {
                     RuntimeException ex = new RuntimeException();
-                    ex.fillInStackTrace();
                     Slog.w(TAG, "Reading tot duration " + src.mDuration
                             + " exceeds total " + mTotalDuration + " in source "
                             + src.mKey.mProcess + " to assoc " + mName, ex);
                 }
                 if (src.mActiveDurations == null && src.mActiveDuration > mTotalDuration) {
                     RuntimeException ex = new RuntimeException();
-                    ex.fillInStackTrace();
                     Slog.w(TAG, "Reading act duration " + src.mActiveDuration
                             + " exceeds total " + mTotalDuration + " in source "
                             + src.mKey.mProcess + " to assoc " + mName, ex);
diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java
index 0dbdb36..7523a2d 100644
--- a/core/java/com/android/internal/app/procstats/ProcessState.java
+++ b/core/java/com/android/internal/app/procstats/ProcessState.java
@@ -538,7 +538,6 @@
     public void incActiveServices(String serviceName) {
         if (DEBUG && "".equals(mName)) {
             RuntimeException here = new RuntimeException("here");
-            here.fillInStackTrace();
             Slog.d(TAG, "incActiveServices: " + this + " service=" + serviceName
                     + " to " + (mNumActiveServices+1), here);
         }
@@ -551,7 +550,6 @@
     public void decActiveServices(String serviceName) {
         if (DEBUG && "".equals(mName)) {
             RuntimeException here = new RuntimeException("here");
-            here.fillInStackTrace();
             Slog.d(TAG, "decActiveServices: " + this + " service=" + serviceName
                     + " to " + (mNumActiveServices-1), here);
         }
@@ -569,7 +567,6 @@
     public void incStartedServices(int memFactor, long now, String serviceName) {
         if (false) {
             RuntimeException here = new RuntimeException("here");
-            here.fillInStackTrace();
             Slog.d(TAG, "incStartedServices: " + this + " service=" + serviceName
                     + " to " + (mNumStartedServices+1), here);
         }
@@ -585,7 +582,6 @@
     public void decStartedServices(int memFactor, long now, String serviceName) {
         if (false) {
             RuntimeException here = new RuntimeException("here");
-            here.fillInStackTrace();
             Slog.d(TAG, "decActiveServices: " + this + " service=" + serviceName
                     + " to " + (mNumStartedServices-1), here);
         }
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index 44c0bd0..2834e68 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -139,7 +139,7 @@
         }
 
         static JankInfo createFromSurfaceControlCallback(SurfaceControl.JankData jankStat) {
-            return new JankInfo(jankStat.frameVsyncId).update(jankStat);
+            return new JankInfo(jankStat.getVsyncId()).update(jankStat);
         }
 
         private JankInfo(long frameVsyncId) {
@@ -154,10 +154,10 @@
 
         private JankInfo update(SurfaceControl.JankData jankStat) {
             this.surfaceControlCallbackFired = true;
-            this.jankType = jankStat.jankType;
-            this.refreshRate = DisplayRefreshRate.getRefreshRate(jankStat.frameIntervalNs);
+            this.jankType = jankStat.getJankType();
+            this.refreshRate = DisplayRefreshRate.getRefreshRate(jankStat.getFrameIntervalNanos());
             if (Flags.useSfFrameDuration()) {
-                this.totalDurationNanos = jankStat.actualAppFrameTimeNs;
+                this.totalDurationNanos = jankStat.getActualAppFrameTimeNanos();
             }
             return this;
         }
@@ -458,14 +458,14 @@
                 }
 
                 for (SurfaceControl.JankData jankStat : jankData) {
-                    if (!isInRange(jankStat.frameVsyncId)) {
+                    if (!isInRange(jankStat.getVsyncId())) {
                         continue;
                     }
-                    JankInfo info = findJankInfo(jankStat.frameVsyncId);
+                    JankInfo info = findJankInfo(jankStat.getVsyncId());
                     if (info != null) {
                         info.update(jankStat);
                     } else {
-                        mJankInfos.put((int) jankStat.frameVsyncId,
+                        mJankInfos.put((int) jankStat.getVsyncId(),
                                 JankInfo.createFromSurfaceControlCallback(jankStat));
                     }
                 }
diff --git a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
index 6b6b81f..48d0d6c 100644
--- a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
+++ b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
@@ -410,6 +410,11 @@
     private int mLocaleConfigRes;
     private boolean mAllowCrossUidActivitySwitchFromBelow;
 
+    @Nullable
+    private int[] mAlternateLauncherIconResIds;
+    @Nullable
+    private int[] mAlternateLauncherLabelResIds;
+
     private List<AndroidPackageSplit> mSplits;
 
     @NonNull
@@ -874,6 +879,18 @@
         return adoptPermissions;
     }
 
+    @Nullable
+    @Override
+    public int[] getAlternateLauncherIconResIds() {
+        return mAlternateLauncherIconResIds;
+    }
+
+    @Nullable
+    @Override
+    public int[] getAlternateLauncherLabelResIds() {
+        return mAlternateLauncherLabelResIds;
+    }
+
     @NonNull
     @Override
     public List<ParsedApexSystemService> getApexSystemServices() {
@@ -1888,6 +1905,19 @@
     }
 
     @Override
+    public PackageImpl setAlternateLauncherIconResIds(@Nullable int[] alternateLauncherIconResIds) {
+        this.mAlternateLauncherIconResIds = alternateLauncherIconResIds;
+        return this;
+    }
+
+    @Override
+    public PackageImpl setAlternateLauncherLabelResIds(
+            @Nullable int[] alternateLauncherLabelResIds) {
+        this.mAlternateLauncherLabelResIds = alternateLauncherLabelResIds;
+        return this;
+    }
+
+    @Override
     public PackageImpl setTaskReparentingAllowed(boolean value) {
         return setBoolean(Booleans.ALLOW_TASK_REPARENTING, value);
     }
@@ -3273,6 +3303,8 @@
         dest.writeLong(this.mBooleans2);
         dest.writeBoolean(this.mAllowCrossUidActivitySwitchFromBelow);
         dest.writeInt(this.mIntentMatchingFlags);
+        dest.writeIntArray(this.mAlternateLauncherIconResIds);
+        dest.writeIntArray(this.mAlternateLauncherLabelResIds);
     }
 
     private void writeFeatureFlagState(@NonNull Parcel dest) {
@@ -3465,6 +3497,8 @@
         this.mBooleans2 = in.readLong();
         this.mAllowCrossUidActivitySwitchFromBelow = in.readBoolean();
         this.mIntentMatchingFlags = in.readInt();
+        this.mAlternateLauncherIconResIds = in.createIntArray();
+        this.mAlternateLauncherLabelResIds = in.createIntArray();
 
         assignDerivedFields();
         assignDerivedFields2();
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
index f4bceb8..67b985a 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
@@ -413,6 +413,18 @@
 
     ParsingPackage setOnBackInvokedCallbackEnabled(boolean enableOnBackInvokedCallback);
 
+    /**
+     * Set the drawable resources id array of the alternate icons that are parsing from the
+     * AndroidManifest file
+     */
+    ParsingPackage setAlternateLauncherIconResIds(int[] alternateLauncherIconResIds);
+
+    /**
+     * Set the string resources id array of the alternate labels that are parsing from the
+     * AndroidManifest file
+     */
+    ParsingPackage setAlternateLauncherLabelResIds(int[] alternateLauncherLabelResIds);
+
     @CallSuper
     ParsedPackage hideAsParsed();
 
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
index 5db7b41..8a6e6be 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
@@ -46,6 +46,7 @@
 import android.content.pm.ConfigurationInfo;
 import android.content.pm.FeatureGroupInfo;
 import android.content.pm.FeatureInfo;
+import android.content.pm.Flags;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.Property;
@@ -154,6 +155,13 @@
 
     private static final String TAG = ParsingUtils.TAG;
 
+    // It is the maximum length of the typedArray of {@link android.R.attr#alternateIcons}
+    // and {@link android.R.attr#alternateLabels}.
+    private static final int MAXIMUM_LAUNCHER_ALTERNATE_IDS_LENGTH = 500;
+
+    private static final String TYPE_STRING = "string";
+    private static final String TYPE_DRAWABLE = "drawable";
+
     public static final boolean DEBUG_JAR = false;
     public static final boolean DEBUG_BACKUP = false;
     public static final float DEFAULT_PRE_O_MAX_ASPECT_RATIO = 1.86f;
@@ -2021,6 +2029,24 @@
                 pkg.setManageSpaceActivityName(manageSpaceActivityName);
             }
 
+            if (Flags.changeLauncherBadging()) {
+                ParseResult<int[]> result = drawableResIdArray(input, sa, res,
+                        R.styleable.AndroidManifestApplication_alternateLauncherIcons,
+                        MAXIMUM_LAUNCHER_ALTERNATE_IDS_LENGTH);
+                if (result.isError()) {
+                    return input.error(result);
+                }
+                pkg.setAlternateLauncherIconResIds(result.getResult());
+
+                result = stringResIdArray(input, sa, res,
+                        R.styleable.AndroidManifestApplication_alternateLauncherLabels,
+                        MAXIMUM_LAUNCHER_ALTERNATE_IDS_LENGTH);
+                if (result.isError()) {
+                    return input.error(result);
+                }
+                pkg.setAlternateLauncherLabelResIds(result.getResult());
+            }
+
             if (pkg.isBackupAllowed()) {
                 // backupAgent, killAfterRestore, fullBackupContent, backupInForeground,
                 // and restoreAnyVersion are only relevant if backup is possible for the
@@ -3395,6 +3421,95 @@
         return sa.getResourceId(attribute, 0);
     }
 
+    /**
+     * Parse the drawable resource id array in the typed array {@code resourceId}
+     * if available. If {@code maxSize} is not zero, only parse and preserve at most
+     * {@code maxSize} ids.
+     */
+    private static ParseResult<int[]> drawableResIdArray(ParseInput input, @NonNull TypedArray sa,
+            @NonNull Resources res, int resourceId, int maxSize) {
+        return resIdArray(input, sa, res, resourceId, TYPE_DRAWABLE, maxSize);
+    }
+
+    /**
+     * Parse the string resource id array in the typed array {@code resourceId}
+     * if available. If {@code maxSize} is not zero, only parse and preserve at most
+     * {@code maxSize} ids.
+     */
+    private static ParseResult<int[]> stringResIdArray(ParseInput input, @NonNull TypedArray sa,
+            @NonNull Resources res, int resourceId, int maxSize) {
+        return resIdArray(input, sa, res, resourceId, TYPE_STRING, maxSize);
+    }
+
+    /**
+     * Parse the resource id array in the typed array {@code resourceId}
+     * if available. If {@code maxSize} is larger than zero, only parse and preserve
+     * at most {@code maxSize} ids that type is matched to the {@code expectedTypeName}.
+     * Because the TypedArray allows mixed types in an array, if {@code expectedTypeName}
+     * is null, it means don't check the type.
+     */
+    private static ParseResult<int[]> resIdArray(ParseInput input, @NonNull TypedArray sa,
+            @NonNull Resources res, int resourceId, @Nullable String expectedTypeName,
+            int maxSize) {
+        if (!sa.hasValue(resourceId)) {
+            return input.success(null);
+        }
+
+        final int typeArrayResId = sa.getResourceId(resourceId, /* defValue= */ 0);
+        if (typeArrayResId == 0) {
+            return input.success(null);
+        }
+
+        // Parse the typedArray
+        try (TypedArray typedArray = res.obtainTypedArray(typeArrayResId)) {
+            final String typedArrayName = res.getResourceName(typeArrayResId);
+            final int length = typedArray.length();
+            if (maxSize > 0 && length > maxSize) {
+                return input.error(TextUtils.formatSimple(
+                        "The length of the typedArray (%s) is larger than %d.",
+                        typedArrayName, maxSize));
+            }
+            Set<Integer> resourceIdSet = new ArraySet<>();
+            for (int i = 0; i < length; i++) {
+                final int id = typedArray.getResourceId(i, /* defValue= */ 0);
+                // Add the id when the conditions are all matched:
+                // 1. The resource Id is not 0
+                // 2. The type is the expected type
+                // 3. The id is not duplicated
+                if (id == 0) {
+                    return input.error(TextUtils.formatSimple(
+                            "There is an item that is not a resource id in the typedArray (%s).",
+                            typedArrayName));
+                }
+
+                try {
+                    if (resourceIdSet.contains(id)) {
+                        return input.error(TextUtils.formatSimple(
+                                "There is a duplicated resource (%s) in the typedArray (%s).",
+                                res.getResourceName(id), typedArrayName));
+                    }
+                    final String typeName = res.getResourceTypeName(id);
+                    if (expectedTypeName != null
+                            && !TextUtils.equals(typeName, expectedTypeName)) {
+                        return input.error(TextUtils.formatSimple(
+                                "There is a resource (%s) in the typedArray (%s) that is not a"
+                                        + " %s type.", res.getResourceName(id), typedArrayName,
+                                expectedTypeName));
+                    }
+                } catch (Resources.NotFoundException e) {
+                    return input.error(TextUtils.formatSimple(
+                            "There is a resource in the typedArray (%s) that is not found in"
+                                    + " the app resources.", typedArrayName));
+                }
+                resourceIdSet.add(id);
+            }
+            if (resourceIdSet.isEmpty()) {
+                return input.success(null);
+            }
+            return input.success(resourceIdSet.stream().mapToInt(i -> i).toArray());
+        }
+    }
+
     private static String string(@StyleableRes int attribute, TypedArray sa) {
         return sa.getString(attribute);
     }
diff --git a/core/java/com/android/server/pm/pkg/AndroidPackage.java b/core/java/com/android/server/pm/pkg/AndroidPackage.java
index 5350059..d05f5e3 100644
--- a/core/java/com/android/server/pm/pkg/AndroidPackage.java
+++ b/core/java/com/android/server/pm/pkg/AndroidPackage.java
@@ -91,6 +91,28 @@
 public interface AndroidPackage {
 
     /**
+     * An array containing the drawable resources that used for the launcher
+     * activity icons.
+     *
+     * @see R.attr#alternateLauncherIcons
+     * @hide
+     */
+    @Immutable.Ignore
+    @Nullable
+    int[] getAlternateLauncherIconResIds();
+
+    /**
+     * An array containing the string resources that used for the launcher
+     * activity labels.
+     *
+     * @see R.attr#alternateLauncherLabels
+     * @hide
+     */
+    @Immutable.Ignore
+    @Nullable
+    int[] getAlternateLauncherLabelResIds();
+
+    /**
      * @see ApplicationInfo#className
      * @see R.styleable#AndroidManifestApplication_name
      */
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 2541258..a21bf9a 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -77,6 +77,7 @@
         "android_os_SystemClock.cpp",
         "android_os_SystemProperties.cpp",
         "android_text_AndroidCharacter.cpp",
+        "android_text_Hyphenator.cpp",
         "android_util_AssetManager.cpp",
         "android_util_EventLog.cpp",
         "android_util_Log.cpp",
@@ -166,7 +167,6 @@
                 "android_view_SurfaceSession.cpp",
                 "android_view_TextureView.cpp",
                 "android_view_TunnelModeEnabledListener.cpp",
-                "android_text_Hyphenator.cpp",
                 "android_os_Debug.cpp",
                 "android_os_GraphicsEnvironment.cpp",
                 "android_os_HidlMemory.cpp",
diff --git a/core/jni/android_text_Hyphenator.cpp b/core/jni/android_text_Hyphenator.cpp
index 933781c..e45cbaf 100644
--- a/core/jni/android_text_Hyphenator.cpp
+++ b/core/jni/android_text_Hyphenator.cpp
@@ -18,10 +18,17 @@
 #include <cutils/trace.h>
 #include <fcntl.h>
 #include <minikin/Hyphenator.h>
+#ifdef __ANDROID__
 #include <sys/mman.h>
+#else
+#include <android-base/mapped_file.h>
+#include <android-base/properties.h>
+#endif
 #include <sys/stat.h>
 #include <sys/types.h>
+#ifdef __ANDROID__
 #include <tracing_perfetto.h>
+#endif
 #include <unicode/uloc.h>
 #include <unistd.h>
 
@@ -30,7 +37,12 @@
 namespace android {
 
 static std::string buildFileName(const std::string& locale) {
+#ifdef __ANDROID__
     constexpr char SYSTEM_HYPHENATOR_PREFIX[] = "/system/usr/hyphen-data/hyph-";
+#else
+    std::string hyphenPath = base::GetProperty("ro.hyphen.data.dir", "/system/usr/hyphen-data");
+    std::string SYSTEM_HYPHENATOR_PREFIX = hyphenPath + "/hyph-";
+#endif
     constexpr char SYSTEM_HYPHENATOR_SUFFIX[] = ".hyb";
     std::string lowerLocale;
     lowerLocale.reserve(locale.size());
@@ -51,11 +63,22 @@
         return std::make_pair(nullptr, 0);
     }
 
+#ifdef __ANDROID__
     void* ptr = mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, fd, 0 /* offset */);
     close(fd);
     if (ptr == MAP_FAILED) {
         return std::make_pair(nullptr, 0);
     }
+#else
+    std::unique_ptr<base::MappedFile> patternFile =
+            base::MappedFile::FromFd(fd, 0, st.st_size, PROT_READ);
+    close(fd);
+    if (patternFile == nullptr) {
+        return std::make_pair(nullptr, 0);
+    }
+    auto* mappedPtr = new base::MappedFile(std::move(*patternFile));
+    char* ptr = mappedPtr->data();
+#endif
     return std::make_pair(reinterpret_cast<const uint8_t*>(ptr), st.st_size);
 }
 
@@ -210,9 +233,13 @@
     addHyphenatorAlias("und-Taml", "ta");  // Tamil
     addHyphenatorAlias("und-Telu", "te");  // Telugu
 
+#ifdef __ANDROID__
     tracing_perfetto::traceBegin(ATRACE_TAG_VIEW, "CacheUnicodeExtensionSubtagsKeyMap");
+#endif
     cacheUnicodeExtensionSubtagsKeyMap();
+#ifdef __ANDROID__
     tracing_perfetto::traceEnd(ATRACE_TAG_VIEW); // CacheUnicodeExtensionSubtagsKeyMap
+#endif
 }
 
 static const JNINativeMethod gMethods[] = {
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index f007cc5..a09c405 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -67,6 +67,7 @@
         jfieldID preferredFrameTimelineIndex;
         jfieldID frameTimelinesLength;
         jfieldID frameTimelines;
+        jfieldID numberQueuedBuffers;
     } vsyncEventDataClassInfo;
 
 } gDisplayEventReceiverClassInfo;
@@ -165,7 +166,8 @@
     return env->NewObject(gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz,
                           gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.init,
                           frameTimelineObjs.get(), vsyncEventData.preferredFrameTimelineIndex,
-                          vsyncEventData.frameTimelinesLength, vsyncEventData.frameInterval);
+                          vsyncEventData.frameTimelinesLength, vsyncEventData.frameInterval,
+                          vsyncEventData.numberQueuedBuffers);
 }
 
 void NativeDisplayEventReceiver::dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId,
@@ -188,6 +190,9 @@
         env->SetLongField(vsyncEventDataObj.get(),
                           gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.frameInterval,
                           vsyncEventData.frameInterval);
+        env->SetIntField(vsyncEventDataObj.get(),
+                         gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.numberQueuedBuffers,
+                         vsyncEventData.numberQueuedBuffers);
 
         ScopedLocalRef<jobjectArray>
                 frameTimelinesObj(env,
@@ -441,7 +446,7 @@
             GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz,
                              "<init>",
                              "([Landroid/view/"
-                             "DisplayEventReceiver$VsyncEventData$FrameTimeline;IIJ)V");
+                             "DisplayEventReceiver$VsyncEventData$FrameTimeline;IIJI)V");
 
     gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.preferredFrameTimelineIndex =
             GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz,
@@ -456,6 +461,9 @@
             GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz,
                             "frameTimelines",
                             "[Landroid/view/DisplayEventReceiver$VsyncEventData$FrameTimeline;");
+    gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.numberQueuedBuffers =
+            GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz,
+                            "numberQueuedBuffers", "I");
 
     return res;
 }
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index d3bf36e..82b463e 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -2163,7 +2163,7 @@
 
 class JankDataListenerWrapper : public JankDataListener {
 public:
-    JankDataListenerWrapper(JNIEnv* env, jobject onJankDataListenerObject) {
+    JankDataListenerWrapper(JNIEnv* env, jobject onJankDataListenerObject) : mRemovedVsyncId(-1) {
         mOnJankDataListenerWeak = env->NewWeakGlobalRef(onJankDataListenerObject);
         env->GetJavaVM(&mVm);
     }
@@ -2174,6 +2174,12 @@
     }
 
     bool onJankDataAvailable(const std::vector<gui::JankData>& jankData) override {
+        // Don't invoke the listener if we've been force removed and got this
+        // out-of-order callback.
+        if (mRemovedVsyncId == 0) {
+            return false;
+        }
+
         JNIEnv* env = getEnv();
 
         jobject target = env->NewLocalRef(mOnJankDataListenerWeak);
@@ -2181,9 +2187,29 @@
             return false;
         }
 
-        jobjectArray jJankDataArray = env->NewObjectArray(jankData.size(),
-                gJankDataClassInfo.clazz, nullptr);
-        for (size_t i = 0; i < jankData.size(); i++) {
+        // Compute the count of data items we'll actually forward to Java.
+        size_t count = 0;
+        if (mRemovedVsyncId <= 0) {
+            count = jankData.size();
+        } else {
+            for (const gui::JankData& frame : jankData) {
+                if (frame.frameVsyncId <= mRemovedVsyncId) {
+                    count++;
+                }
+            }
+        }
+
+        if (count == 0) {
+            return false;
+        }
+
+        jobjectArray jJankDataArray = env->NewObjectArray(count, gJankDataClassInfo.clazz, nullptr);
+        for (size_t i = 0, j = 0; i < jankData.size() && j < count; i++) {
+            // Filter any data for frames past our removal vsync.
+            if (mRemovedVsyncId > 0 && jankData[i].frameVsyncId > mRemovedVsyncId) {
+                continue;
+            }
+
             // The exposed constants in SurfaceControl are simplified, so we need to translate the
             // jank type we get from SF to what is exposed in Java.
             int sfJankType = jankData[i].jankType;
@@ -2210,7 +2236,7 @@
                                    jankData[i].frameVsyncId, javaJankType,
                                    jankData[i].frameIntervalNs, jankData[i].scheduledAppFrameTimeNs,
                                    jankData[i].actualAppFrameTimeNs);
-            env->SetObjectArrayElement(jJankDataArray, i, jJankData);
+            env->SetObjectArrayElement(jJankDataArray, j++, jJankData);
             env->DeleteLocalRef(jJankData);
         }
 
@@ -2225,6 +2251,11 @@
         return true;
     }
 
+    void removeListener(int64_t afterVsyncId) {
+        mRemovedVsyncId = (afterVsyncId <= 0) ? 0 : afterVsyncId;
+        JankDataListener::removeListener(afterVsyncId);
+    }
+
 private:
 
     JNIEnv* getEnv() {
@@ -2235,6 +2266,7 @@
 
     JavaVM* mVm;
     jobject mOnJankDataListenerWeak;
+    int64_t mRemovedVsyncId;
 };
 
 static jlong nativeCreateJankDataListenerWrapper(JNIEnv* env, jclass clazz,
diff --git a/core/jni/platform/host/HostRuntime.cpp b/core/jni/platform/host/HostRuntime.cpp
index 7fca117..1a03283 100644
--- a/core/jni/platform/host/HostRuntime.cpp
+++ b/core/jni/platform/host/HostRuntime.cpp
@@ -88,6 +88,7 @@
 extern int register_android_os_SystemClock(JNIEnv* env);
 extern int register_android_os_SystemProperties(JNIEnv* env);
 extern int register_android_text_AndroidCharacter(JNIEnv* env);
+extern int register_android_text_Hyphenator(JNIEnv* env);
 extern int register_android_util_EventLog(JNIEnv* env);
 extern int register_android_util_Log(JNIEnv* env);
 extern int register_android_util_jar_StrictJarFile(JNIEnv* env);
@@ -133,6 +134,7 @@
         {"android.os.SystemClock", REG_JNI(register_android_os_SystemClock)},
         {"android.os.SystemProperties", REG_JNI(register_android_os_SystemProperties)},
         {"android.text.AndroidCharacter", REG_JNI(register_android_text_AndroidCharacter)},
+        {"android.text.Hyphenator", REG_JNI(register_android_text_Hyphenator)},
         {"android.util.EventLog", REG_JNI(register_android_util_EventLog)},
         {"android.util.Log", REG_JNI(register_android_util_Log)},
         {"android.util.jar.StrictJarFile", REG_JNI(register_android_util_jar_StrictJarFile)},
diff --git a/core/tests/coretests/src/android/app/activity/ActivityTestsBase.java b/core/tests/coretests/src/android/app/activity/ActivityTestsBase.java
index 232abe2..7f069ad 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityTestsBase.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityTestsBase.java
@@ -22,7 +22,7 @@
 import android.test.AndroidTestCase;
 import android.test.PerformanceTestCase;
 
-public class ActivityTestsBase extends AndroidTestCase 
+public class ActivityTestsBase extends AndroidTestCase
         implements PerformanceTestCase, LaunchpadActivity.CallingTest {
     public static final String PERMISSION_GRANTED =
             "com.android.frameworks.coretests.permission.TEST_GRANTED";
@@ -111,7 +111,6 @@
 
     public void finishWithResult(int resultCode, Intent data) {
         RuntimeException where = new RuntimeException("Original error was here");
-        where.fillInStackTrace();
         finishWithResult(resultCode, data, where);
     }
 
@@ -194,15 +193,15 @@
     public int getResultCode() {
         return mResultCode;
     }
-    
+
     public Intent getResultData() {
         return mData;
     }
-    
+
     public RuntimeException getResultStack() {
         return mResultStack;
     }
-    
+
     public void onTimeout() {
         String msg = mExpecting == null
                 ? "Timeout" : ("Timeout while expecting " + mExpecting);
diff --git a/core/tests/coretests/src/android/app/activity/LaunchpadActivity.java b/core/tests/coretests/src/android/app/activity/LaunchpadActivity.java
index fda249f..9b358e0 100644
--- a/core/tests/coretests/src/android/app/activity/LaunchpadActivity.java
+++ b/core/tests/coretests/src/android/app/activity/LaunchpadActivity.java
@@ -461,7 +461,6 @@
         mResultCode = resultCode;
         mData = data;
         mResultStack = new RuntimeException("Original error was here");
-        mResultStack.fillInStackTrace();
     }
 
     private void registerMyReceiver(IntentFilter filter) {
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/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 02f5955..d415c10 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -420,7 +420,8 @@
         }
 
         // Update the src-rect-hint in params in place, to set up initial animator transform.
-        params.getSourceRectHint().set(adjustedSourceRectHint);
+        params.copyOnlySet(new PictureInPictureParams.Builder()
+                .setSourceRectHint(adjustedSourceRectHint).build());
 
         // Config-at-end transitions need to have their activities transformed before starting
         // the animation; this makes the buffer seem like it's been updated to final size.
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MaximizeAppWindowWithDragToTopDragZoneTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MaximizeAppWindowWithDragToTopDragZoneTest.kt
new file mode 100644
index 0000000..7e0b81a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MaximizeAppWindowWithDragToTopDragZoneTest.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.wm.shell.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.scenarios.MaximizeAppWindowWithDragToTopDragZone
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/* Functional test for [MaximizeAppWindowWithDragToTopDragZone]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+class MaximizeAppWindowWithDragToTopDragZoneTest : MaximizeAppWindowWithDragToTopDragZone()
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindowWithDragToTopDragZone.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindowWithDragToTopDragZone.kt
new file mode 100644
index 0000000..a2b88f2
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MaximizeAppWindowWithDragToTopDragZone.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.wm.shell.scenarios
+
+import android.app.Instrumentation
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.flicker.rules.ChangeDisplayOrientationRule
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.internal.R
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+/**
+ * Base scenario test for maximizing a desktop app window by dragging it to the top drag zone.
+ */
+@Ignore("Test Base Class")
+abstract class MaximizeAppWindowWithDragToTopDragZone
+constructor(private val rotation: Rotation = Rotation.ROTATION_0) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+
+    @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+        // Skip the test when the drag-to-maximize is disabled on this device.
+        Assume.assumeTrue(Flags.enableDragToMaximize() &&
+            instrumentation.context.resources.getBoolean(R.bool.config_dragToMaximizeInDesktopMode))
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+        ChangeDisplayOrientationRule.setRotation(rotation)
+        testApp.enterDesktopWithDrag(wmHelper, device)
+    }
+
+    @Test
+    open fun maximizeAppWithDragToTopDragZone() {
+        testApp.maximizeAppWithDragToTopDragZone(wmHelper, device)
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+    }
+}
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 2ae89d3..82e9503 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -16,6 +16,7 @@
 
 package android.media;
 
+import static android.media.codec.Flags.FLAG_CODEC_AVAILABILITY;
 import static android.media.codec.Flags.FLAG_NULL_OUTPUT_SURFACE;
 import static android.media.codec.Flags.FLAG_REGION_OF_INTEREST;
 import static android.media.codec.Flags.FLAG_SUBSESSION_METRICS;
@@ -29,6 +30,7 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.ImageFormat;
 import android.graphics.Rect;
@@ -1843,6 +1845,12 @@
      */
     private static final int CB_METRICS_FLUSHED = 8;
 
+    /**
+     * Callback ID to notify the change in resource requirement
+     * for the codec component.
+     */
+    private static final int CB_REQUIRED_RESOURCES_CHANGE = 9;
+
     private class EventHandler extends Handler {
         private MediaCodec mCodec;
 
@@ -2017,13 +2025,19 @@
 
                 case CB_METRICS_FLUSHED:
                 {
-
                     if (GetFlag(() -> android.media.codec.Flags.subsessionMetrics())) {
                         mCallback.onMetricsFlushed(mCodec, (PersistableBundle)msg.obj);
                     }
                     break;
                 }
 
+                case CB_REQUIRED_RESOURCES_CHANGE: {
+                    if (android.media.codec.Flags.codecAvailability()) {
+                        mCallback.onRequiredResourcesChanged(mCodec);
+                    }
+                    break;
+                }
+
                 default:
                 {
                     break;
@@ -2302,6 +2316,70 @@
     }
 
     /**
+     * @hide
+     * Abstraction for the Global Codec resources.
+     * This encapsulates all the available codec resources on the device.
+     *
+     * To be able to enforce and test the implementation of codec availability hal APIs,
+     * globally available codec resources are exposed only as TestApi.
+     * This will be tracked and verified through cts.
+     */
+    @FlaggedApi(FLAG_CODEC_AVAILABILITY)
+    @TestApi
+    public static final class GlobalResourceInfo {
+        /**
+         * Identifier for the Resource type.
+         */
+        String mName;
+        /**
+         * Total count/capacity of resources of this type.
+         */
+        long mCapacity;
+        /**
+         * Available count of this resource type.
+         */
+        long mAvailable;
+
+        @NonNull
+        public String getName() {
+            return mName;
+        }
+
+        public long getCapacity() {
+            return mCapacity;
+        }
+
+        public long getAvailable() {
+            return mAvailable;
+        }
+    };
+
+    /**
+     * @hide
+     * Get a list of globally available codec resources.
+     *
+     * To be able to enforce and test the implementation of codec availability hal APIs,
+     * it is exposed only as TestApi.
+     * This will be tracked and verified through cts.
+     *
+     * This returns a {@link java.util.List} list of codec resources.
+     * For every {@link GlobalResourceInfo} in the list, it encapsulates the
+     * information about each resources available globaly on device.
+     *
+     * @return A list of available device codec resources; an empty list if no
+     *         device codec resources are available.
+     * @throws UnsupportedOperationException if not implemented.
+     */
+    @FlaggedApi(FLAG_CODEC_AVAILABILITY)
+    @TestApi
+    public static @NonNull List<GlobalResourceInfo> getGloballyAvailableResources() {
+        return native_getGloballyAvailableResources();
+    }
+
+    @NonNull
+    private static native List<GlobalResourceInfo> native_getGloballyAvailableResources();
+
+    /**
      * Configures a component.
      *
      * @param format The format of the input data (decoder) or the desired
@@ -2443,6 +2521,73 @@
     }
 
     /**
+     * @hide
+     * Abstraction for the resources associated with a codec instance.
+     * This encapsulates the required codec resources for a configured codec instance.
+     *
+     * To be able to enforce and test the implementation of codec availability hal APIs,
+     * required codec resources are exposed only as TestApi.
+     * This will be tracked and verified through cts.
+     */
+    @FlaggedApi(FLAG_CODEC_AVAILABILITY)
+    @TestApi
+    public static final class InstanceResourceInfo {
+        /**
+         * Identifier for the Resource type.
+         */
+        String mName;
+        /**
+         * Required resource count of this type.
+         */
+        long mStaticCount;
+        /**
+         * Per frame resource requirement of this resource type.
+         */
+        long mPerFrameCount;
+
+        @NonNull
+        public String getName() {
+            return mName;
+        }
+
+        public long getStaticCount() {
+            return mStaticCount;
+        }
+
+        public long getPerFrameCount() {
+            return mPerFrameCount;
+        }
+    };
+
+    /**
+     * @hide
+     * Get a list of required codec resources for this configuration.
+     *
+     * To be able to enforce and test the implementation of codec availability hal APIs,
+     * it is exposed only as TestApi.
+     * This will be tracked and verified through cts.
+     *
+     * This returns a {@link java.util.List} list of codec resources.
+     * For every {@link GlobalResourceInfo} in the list, it encapsulates the
+     * information about each resources required for the current configuration.
+     *
+     * NOTE: This may only be called after {@link #configure}.
+     *
+     * @return A list of required device codec resources; an empty list if no
+     *         device codec resources are required.
+     * @throws IllegalStateException if the codec wasn't configured yet.
+     * @throws UnsupportedOperationException if not implemented.
+     */
+    @FlaggedApi(FLAG_CODEC_AVAILABILITY)
+    @TestApi
+    public @NonNull List<InstanceResourceInfo> getRequiredResources() {
+        return native_getRequiredResources();
+    }
+
+    @NonNull
+    private native List<InstanceResourceInfo> native_getRequiredResources();
+
+    /**
      *  Dynamically sets the output surface of a codec.
      *  <p>
      *  This can only be used if the codec was configured with an output surface.  The
@@ -5740,6 +5885,25 @@
                 @NonNull MediaCodec codec, @NonNull PersistableBundle metrics) {
             // default implementation ignores this callback.
         }
+
+        /**
+         * @hide
+         * Called when there is a change in the required resources for the codec.
+         * <p>
+         * Upon receiving this notification, the updated resource requirement
+         * can be queried through {@link #getRequiredResources}.
+         *
+         * @param codec The MediaCodec object.
+         */
+        @FlaggedApi(FLAG_CODEC_AVAILABILITY)
+        @TestApi
+        public void onRequiredResourcesChanged(@NonNull MediaCodec codec) {
+            /*
+             * A default implementation for backward compatibility.
+             * Since this is a TestApi, we are not enforcing the callback to be
+             * overridden.
+             */
+        }
     }
 
     private void postEventFromNative(
diff --git a/media/java/android/media/tv/flags/media_tv.aconfig b/media/java/android/media/tv/flags/media_tv.aconfig
index 4de6863..6441652 100644
--- a/media/java/android/media/tv/flags/media_tv.aconfig
+++ b/media/java/android/media/tv/flags/media_tv.aconfig
@@ -80,3 +80,11 @@
     description : "Feature flag to add setResourceHolderRetain api to MediaCas and Tuner JAVA."
     bug: "372973197"
 }
+
+flag {
+    name: "apply_picture_profiles"
+    is_exported: true
+    namespace: "media_tv"
+    description : "Feature flag to enable APIs for applying picture profiles"
+    bug: "337330263"
+}
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index c44e26f..f09dc72 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -104,6 +104,7 @@
         "libgrallocusage",
         "libmedia_midiiowrapper",
         "android.companion.virtualdevice.flags-aconfig-cc",
+        "android.media.codec-aconfig-cc",
         "android.media.playback.flags-aconfig-cc",
     ],
 
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 001653b..fc184fe 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -39,6 +39,8 @@
 #include <C2Buffer.h>
 #include <C2PlatformSupport.h>
 
+#include <android_media_codec.h>
+
 #include <android/hardware/cas/native/1.0/IDescrambler.h>
 
 #include <android_runtime/android_hardware_HardwareBuffer.h>
@@ -189,6 +191,22 @@
     jmethodID setId;
 } gBufferInfo;
 
+static struct {
+    jclass clazz;
+    jmethodID ctorId;
+    jfieldID resourceId;
+    jfieldID capacityId;
+    jfieldID availableId;
+} gGlobalResourceInfo;
+
+static struct {
+    jclass clazz;
+    jmethodID ctorId;
+    jfieldID resourceId;
+    jfieldID staticCountId;
+    jfieldID perFrameCountId;
+} gInstanceResourceInfo;
+
 struct fields_t {
     jmethodID postEventFromNativeID;
     jmethodID lockAndGetContextID;
@@ -1129,6 +1147,37 @@
     return mCodec->unsubscribeFromVendorParameters(names);
 }
 
+static jobject getJavaResources(
+        JNIEnv *env,
+        const std::vector<MediaCodec::InstanceResourceInfo>& resources) {
+    jobject resourcesObj = env->NewObject(gArrayListInfo.clazz, gArrayListInfo.ctorId);
+    for (const MediaCodec::InstanceResourceInfo& res : resources) {
+        ScopedLocalRef<jobject> object{env, env->NewObject(
+                gInstanceResourceInfo.clazz, gInstanceResourceInfo.ctorId)};
+        ScopedLocalRef<jstring> nameStr{env, env->NewStringUTF(res.mName.c_str())};
+        env->SetObjectField(object.get(), gInstanceResourceInfo.resourceId, nameStr.get());
+        env->SetLongField(object.get(),
+                          gInstanceResourceInfo.staticCountId,
+                          (jlong)res.mStaticCount);
+        env->SetLongField(object.get(),
+                          gInstanceResourceInfo.perFrameCountId,
+                          (jlong)res.mPerFrameCount);
+        (void)env->CallBooleanMethod(resourcesObj, gArrayListInfo.addId, object.get());
+    }
+
+    return resourcesObj;
+}
+
+status_t JMediaCodec::getRequiredResources(JNIEnv *env, jobject *resourcesObj) {
+    std::vector<MediaCodec::InstanceResourceInfo> resources;
+    status_t status = mCodec->getRequiredResources(resources);
+    if (status != OK) {
+        return status;
+    }
+    *resourcesObj = getJavaResources(env, resources);
+    return OK;
+}
+
 static jthrowable createCodecException(
         JNIEnv *env, status_t err, int32_t actionCode, const char *msg = NULL) {
     ScopedLocalRef<jclass> clazz(
@@ -1475,6 +1524,10 @@
             obj = MediaMetricsJNI::writeMetricsToBundle(env, item, NULL);
             break;
         }
+        case MediaCodec::CB_REQUIRED_RESOURCES_CHANGED:
+        {
+            break;
+        }
 
         default:
             TRESPASS();
@@ -3560,6 +3613,64 @@
     return;
 }
 
+static jobject getJavaResources(
+        JNIEnv *env,
+        const std::vector<MediaCodec::GlobalResourceInfo>& resources) {
+    jobject resourcesObj = env->NewObject(gArrayListInfo.clazz, gArrayListInfo.ctorId);
+    for (const MediaCodec::GlobalResourceInfo& res : resources) {
+        ScopedLocalRef<jobject> object{env, env->NewObject(
+                gGlobalResourceInfo.clazz, gGlobalResourceInfo.ctorId)};
+        ScopedLocalRef<jstring> nameStr{env, env->NewStringUTF(res.mName.c_str())};
+        env->SetObjectField(object.get(), gInstanceResourceInfo.resourceId, nameStr.get());
+        env->SetLongField(object.get(), gGlobalResourceInfo.capacityId, (jlong)res.mCapacity);
+        env->SetLongField(object.get(), gGlobalResourceInfo.availableId, (jlong)res.mAvailable);
+        (void)env->CallBooleanMethod(resourcesObj, gArrayListInfo.addId, object.get());
+    }
+
+    return resourcesObj;
+}
+
+static jobject android_media_MediaCodec_getGloballyAvailableResources(
+        JNIEnv *env, jobject thiz) {
+    (void)thiz;
+    std::vector<MediaCodec::GlobalResourceInfo> resources;
+    status_t status = MediaCodec::getGloballyAvailableResources(resources);
+    if (status != OK) {
+        if (status == ERROR_UNSUPPORTED) {
+            jniThrowException(env, "java/lang/UnsupportedOperationException",
+                              "Function Not Implemented");
+        } else {
+            throwExceptionAsNecessary(env, status, nullptr);
+        }
+        return nullptr;
+    }
+
+    return getJavaResources(env, resources);
+}
+
+static jobject android_media_MediaCodec_getRequiredResources(
+        JNIEnv *env, jobject thiz) {
+    sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+    if (codec == nullptr || codec->initCheck() != OK) {
+        throwExceptionAsNecessary(env, INVALID_OPERATION, codec);
+        return nullptr;
+    }
+
+    jobject ret = nullptr;
+    status_t status = codec->getRequiredResources(env, &ret);
+    if (status != OK) {
+        if (status == ERROR_UNSUPPORTED) {
+            jniThrowException(env, "java/lang/UnsupportedOperationException",
+                              "Function Not Implemented");
+        } else {
+            throwExceptionAsNecessary(env, status, nullptr);
+        }
+        return nullptr;
+    }
+
+    return ret;
+}
+
 static void android_media_MediaCodec_native_init(JNIEnv *env, jclass) {
     ScopedLocalRef<jclass> clazz(
             env, env->FindClass("android/media/MediaCodec"));
@@ -3905,6 +4016,36 @@
     gFields.bufferInfoOffset = env->GetFieldID(clazz.get(), "offset", "I");
     gFields.bufferInfoPresentationTimeUs =
             env->GetFieldID(clazz.get(), "presentationTimeUs", "J");
+
+    // Since these TestApis are defined under the flag, make sure they are
+    // accessed only when the flag is set.
+    if (android::media::codec::codec_availability()) {
+        clazz.reset(env->FindClass("android/media/MediaCodec$GlobalResourceInfo"));
+        CHECK(clazz.get() != NULL);
+        gGlobalResourceInfo.clazz = (jclass)env->NewGlobalRef(clazz.get());
+        gGlobalResourceInfo.ctorId = env->GetMethodID(clazz.get(), "<init>", "()V");
+        CHECK(gGlobalResourceInfo.ctorId != NULL);
+        gGlobalResourceInfo.resourceId =
+                env->GetFieldID(clazz.get(), "mName", "Ljava/lang/String;");
+        CHECK(gGlobalResourceInfo.resourceId != NULL);
+        gGlobalResourceInfo.capacityId = env->GetFieldID(clazz.get(), "mCapacity", "J");
+        CHECK(gGlobalResourceInfo.capacityId != NULL);
+        gGlobalResourceInfo.availableId = env->GetFieldID(clazz.get(), "mAvailable", "J");
+        CHECK(gGlobalResourceInfo.availableId != NULL);
+
+        clazz.reset(env->FindClass("android/media/MediaCodec$InstanceResourceInfo"));
+        CHECK(clazz.get() != NULL);
+        gInstanceResourceInfo.clazz = (jclass)env->NewGlobalRef(clazz.get());
+        gInstanceResourceInfo.ctorId = env->GetMethodID(clazz.get(), "<init>", "()V");
+        CHECK(gInstanceResourceInfo.ctorId != NULL);
+        gInstanceResourceInfo.resourceId =
+                env->GetFieldID(clazz.get(), "mName", "Ljava/lang/String;");
+        CHECK(gInstanceResourceInfo.resourceId != NULL);
+        gInstanceResourceInfo.staticCountId= env->GetFieldID(clazz.get(), "mStaticCount", "J");
+        CHECK(gInstanceResourceInfo.staticCountId != NULL);
+        gInstanceResourceInfo.perFrameCountId = env->GetFieldID(clazz.get(), "mPerFrameCount", "J");
+        CHECK(gInstanceResourceInfo.perFrameCountId != NULL);
+    }
 }
 
 static void android_media_MediaCodec_native_setup(
@@ -4261,6 +4402,12 @@
 
     { "native_finalize", "()V",
       (void *)android_media_MediaCodec_native_finalize },
+
+    { "native_getGloballyAvailableResources", "()Ljava/util/List;",
+      (void *)android_media_MediaCodec_getGloballyAvailableResources},
+
+    { "native_getRequiredResources", "()Ljava/util/List;",
+      (void *)android_media_MediaCodec_getRequiredResources},
 };
 
 static const JNINativeMethod gLinearBlockMethods[] = {
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index c9b6b7f6..930dbbe 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -185,6 +185,8 @@
 
     status_t unsubscribeFromVendorParameters(JNIEnv *env, jobject names);
 
+    status_t getRequiredResources(JNIEnv *env, jobject *resourcesObj);
+
     bool hasCryptoOrDescrambler() { return mHasCryptoOrDescrambler; }
 
     const sp<ICrypto> &getCrypto() { return mCrypto; }
diff --git a/nfc/java/android/nfc/OemLogItems.java b/nfc/java/android/nfc/OemLogItems.java
index 6671941..4f3e199 100644
--- a/nfc/java/android/nfc/OemLogItems.java
+++ b/nfc/java/android/nfc/OemLogItems.java
@@ -142,8 +142,11 @@
         dest.writeByteArray(mCommandApdus);

         dest.writeInt(mResponseApdus.length);

         dest.writeByteArray(mResponseApdus);

-        dest.writeLong(mRfFieldOnTime.getEpochSecond());

-        dest.writeInt(mRfFieldOnTime.getNano());

+        dest.writeBoolean(mRfFieldOnTime != null);

+        if (mRfFieldOnTime != null) {

+            dest.writeLong(mRfFieldOnTime.getEpochSecond());

+            dest.writeInt(mRfFieldOnTime.getNano());

+        }

         dest.writeParcelable(mTag, 0);

     }

 

@@ -305,7 +308,12 @@
         in.readByteArray(this.mCommandApdus);

         this.mResponseApdus = new byte[in.readInt()];

         in.readByteArray(this.mResponseApdus);

-        this.mRfFieldOnTime = Instant.ofEpochSecond(in.readLong(), in.readInt());

+        boolean isRfFieldOnTimeSet = in.readBoolean();

+        if (isRfFieldOnTimeSet) {

+            this.mRfFieldOnTime = Instant.ofEpochSecond(in.readLong(), in.readInt());

+        } else {

+            this.mRfFieldOnTime = null;

+        }

         this.mTag = in.readParcelable(Tag.class.getClassLoader(), Tag.class);

     }

 

diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index b2dcb7f..a3da93d 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -61,9 +61,11 @@
         "SettingsLibUtils",
         "SettingsLibZeroStatePreference",
         "settingslib_media_flags_lib",
+    ],
+    libs:[
+        // This flag library has been added in frameworks jar
         "aconfig_settingslib_flags_java_lib",
     ],
-
     plugins: ["androidx.room_room-compiler-plugin"],
     use_resource_processor: true,
     resource_dirs: ["res"],
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPageProvider.kt
index 4d3a78a5..f2bc380 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPageProvider.kt
@@ -17,8 +17,12 @@
 package com.android.settingslib.spa.gallery.ui
 
 import android.os.Bundle
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.height
 import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
 import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
 import com.android.settingslib.spa.framework.common.SettingsEntry
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
@@ -30,6 +34,7 @@
 import com.android.settingslib.spa.widget.preference.SimplePreferenceMacro
 import com.android.settingslib.spa.widget.scaffold.RegularScaffold
 import com.android.settingslib.spa.widget.ui.Category
+import com.android.settingslib.spa.widget.ui.LazyCategory
 
 private const val TITLE = "Sample Category"
 
@@ -65,7 +70,7 @@
         )
         entryList.add(
             SettingsEntryBuilder.create("Preference 3", owner)
-                .setMacro { SimplePreferenceMacro(title = "Preference 2", summary = "Summary 3") }
+                .setMacro { SimplePreferenceMacro(title = "Preference 3", summary = "Summary 3") }
                 .build()
         )
         entryList.add(
@@ -88,6 +93,13 @@
                 entries[2].UiLayout()
                 entries[3].UiLayout()
             }
+            Column(Modifier.height(200.dp)) {
+                LazyCategory(
+                    list = entries,
+                    entry = { index: Int -> @Composable { entries[index].UiLayout() } },
+                    title = { index: Int -> if (index == 0 || index == 2) "LazyCategory" else null },
+                ) {}
+            }
         }
     }
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt
index 66680fa..28b2b4a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt
@@ -19,8 +19,13 @@
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyListState
+import androidx.compose.foundation.lazy.rememberLazyListState
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.outlined.TouchApp
 import androidx.compose.material3.MaterialTheme
@@ -34,6 +39,7 @@
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import com.android.settingslib.spa.framework.theme.SettingsDimension
 import com.android.settingslib.spa.framework.theme.SettingsShape
@@ -98,6 +104,57 @@
     }
 }
 
+/**
+ * A container that is used to group items with lazy loading.
+ *
+ * @param list The list of items to display.
+ * @param entry The entry for each list item according to its index in list.
+ * @param key Optional. The key for each item in list to provide unique item identifiers, making
+ * the list more efficient.
+ * @param title Optional. Category title for each item or each group of items in the list. It
+ * should be decided by the index.
+ * @param bottomPadding Optional. Bottom outside padding of the category.
+ * @param state Optional. State of LazyList.
+ * @param content Optional. Content to be shown at the top of the category.
+ */
+
+@Composable
+fun LazyCategory(
+    list: List<Any>,
+    entry: (Int) -> @Composable () -> Unit,
+    key: ((Int) -> Any)? = null,
+    title: ((Int) -> String?)? = null,
+    bottomPadding: Dp = SettingsDimension.paddingSmall,
+    state: LazyListState = rememberLazyListState(),
+    content: @Composable () -> Unit,
+) {
+    Column(
+        Modifier.padding(
+                PaddingValues(
+                    start = SettingsDimension.paddingLarge,
+                    end = SettingsDimension.paddingLarge,
+                    top = SettingsDimension.paddingSmall,
+                    bottom = bottomPadding,
+                )
+            )
+            .clip(SettingsShape.CornerMedium2)
+    ) {
+        LazyColumn(
+            modifier = Modifier.fillMaxSize(),
+            verticalArrangement = Arrangement.spacedBy(SettingsDimension.paddingTiny),
+            state = state,
+        ) {
+            item { content() }
+
+            items(count = list.size, key = key) {
+                title?.invoke(it)?.let { title -> CategoryTitle(title) }
+                val entryPreference = entry(it)
+                entryPreference()
+            }
+        }
+    }
+}
+
 @Preview
 @Composable
 private fun CategoryPreview() {
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CategoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CategoryTest.kt
index 09a6e6d..4b4a8c2 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CategoryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CategoryTest.kt
@@ -16,10 +16,16 @@
 
 package com.android.settingslib.spa.widget.ui
 
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.height
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
 import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
@@ -30,14 +36,11 @@
 @RunWith(AndroidJUnit4::class)
 class CategoryTest {
 
-    @get:Rule
-    val composeTestRule = createComposeRule()
+    @get:Rule val composeTestRule = createComposeRule()
 
     @Test
     fun categoryTitle() {
-        composeTestRule.setContent {
-            CategoryTitle(title = "CategoryTitle")
-        }
+        composeTestRule.setContent { CategoryTitle(title = "CategoryTitle") }
 
         composeTestRule.onNodeWithText("CategoryTitle").assertIsDisplayed()
     }
@@ -46,12 +49,14 @@
     fun category_hasContent_titleDisplayed() {
         composeTestRule.setContent {
             Category(title = "CategoryTitle") {
-                Preference(remember {
-                    object : PreferenceModel {
-                        override val title = "Some Preference"
-                        override val summary = { "Some summary" }
+                Preference(
+                    remember {
+                        object : PreferenceModel {
+                            override val title = "Some Preference"
+                            override val summary = { "Some summary" }
+                        }
                     }
-                })
+                )
             }
         }
 
@@ -60,10 +65,45 @@
 
     @Test
     fun category_noContent_titleNotDisplayed() {
-        composeTestRule.setContent {
-            Category(title = "CategoryTitle") {}
-        }
+        composeTestRule.setContent { Category(title = "CategoryTitle") {} }
 
         composeTestRule.onNodeWithText("CategoryTitle").assertDoesNotExist()
     }
+
+    @Test
+    fun lazyCategory_content_displayed() {
+        composeTestRule.setContent { TestLazyCategory() }
+
+        composeTestRule.onNodeWithText("text").assertExists()
+    }
+
+    @Test
+    fun lazyCategory_title_displayed() {
+        composeTestRule.setContent { TestLazyCategory() }
+
+        composeTestRule.onNodeWithText("LazyCategory 0").assertExists()
+        composeTestRule.onNodeWithText("LazyCategory 1").assertDoesNotExist()
+    }
+}
+
+@Composable
+private fun TestLazyCategory() {
+    val list: List<PreferenceModel> =
+        listOf(
+            object : PreferenceModel {
+                override val title = "title"
+            },
+            object : PreferenceModel {
+                override val title = "title"
+            },
+        )
+    Column(Modifier.height(200.dp)) {
+        LazyCategory(
+            list = list,
+            entry = { index: Int -> @Composable { Preference(list[index]) } },
+            title = { index: Int -> if (index == 0) "LazyCategory $index" else null },
+        ) {
+            Text("text")
+        }
+    }
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
index bededf0..2a214b6 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
@@ -37,7 +37,9 @@
 import com.android.settingslib.spa.framework.compose.LogCompositions
 import com.android.settingslib.spa.framework.compose.TimeMeasurer.Companion.rememberTimeMeasurer
 import com.android.settingslib.spa.framework.compose.rememberLazyListStateAndHideKeyboardWhenStartScroll
+import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled
 import com.android.settingslib.spa.widget.ui.CategoryTitle
+import com.android.settingslib.spa.widget.ui.LazyCategory
 import com.android.settingslib.spa.widget.ui.PlaceholderTitle
 import com.android.settingslib.spa.widget.ui.Spinner
 import com.android.settingslib.spa.widget.ui.SpinnerOption
@@ -55,19 +57,14 @@
 private const val TAG = "AppList"
 private const val CONTENT_TYPE_HEADER = "header"
 
-/**
- * The config used to load the App List.
- */
+/** The config used to load the App List. */
 data class AppListConfig(
     val userIds: List<Int>,
     val showInstantApps: Boolean,
     val matchAnyUserForAdmin: Boolean,
 )
 
-data class AppListState(
-    val showSystem: () -> Boolean,
-    val searchQuery: () -> String,
-)
+data class AppListState(val showSystem: () -> Boolean, val searchQuery: () -> String)
 
 data class AppListInput<T : AppRecord>(
     val config: AppListConfig,
@@ -90,7 +87,7 @@
 
 @Composable
 internal fun <T : AppRecord> AppListInput<T>.AppListImpl(
-    viewModelSupplier: @Composable () -> IAppListViewModel<T>,
+    viewModelSupplier: @Composable () -> IAppListViewModel<T>
 ) {
     LogCompositions(TAG, config.userIds.toString())
     val viewModel = viewModelSupplier()
@@ -125,7 +122,7 @@
     appListData: State<AppListData<T>?>,
     header: @Composable () -> Unit,
     bottomPadding: Dp,
-    noItemMessage: String?
+    noItemMessage: String?,
 ) {
     val timeMeasurer = rememberTimeMeasurer(TAG)
     appListData.value?.let { (list, option) ->
@@ -135,40 +132,61 @@
             PlaceholderTitle(noItemMessage ?: stringResource(R.string.no_applications))
             return
         }
-        LazyColumn(
-            modifier = Modifier.fillMaxSize(),
-            state = rememberLazyListStateAndHideKeyboardWhenStartScroll(),
-            contentPadding = PaddingValues(bottom = bottomPadding),
-        ) {
-            item(contentType = CONTENT_TYPE_HEADER) {
+        if (isSpaExpressiveEnabled) {
+            LazyCategory(
+                list = list,
+                entry = { index: Int ->
+                    @Composable {
+                        val appEntry = list[index]
+                        val summary = getSummary(option, appEntry.record) ?: { "" }
+                        remember(appEntry) {
+                                AppListItemModel(appEntry.record, appEntry.label, summary)
+                            }
+                            .AppItem()
+                    }
+                },
+                key = { index: Int -> list[index].record.itemKey(option) },
+                title = { index: Int -> getGroupTitle(option, list[index].record) },
+                bottomPadding = bottomPadding,
+                state = rememberLazyListStateAndHideKeyboardWhenStartScroll(),
+            ) {
                 header()
             }
+        } else {
+            LazyColumn(
+                modifier = Modifier.fillMaxSize(),
+                state = rememberLazyListStateAndHideKeyboardWhenStartScroll(),
+                contentPadding = PaddingValues(bottom = bottomPadding),
+            ) {
+                item(contentType = CONTENT_TYPE_HEADER) { header() }
 
-            items(count = list.size, key = { list[it].record.itemKey(option) }) {
-                remember(list) { getGroupTitleIfFirst(option, list, it) }
-                    ?.let { group -> CategoryTitle(title = group) }
+                items(count = list.size, key = { list[it].record.itemKey(option) }) {
+                    remember(list) { getGroupTitleIfFirst(option, list, it) }
+                        ?.let { group -> CategoryTitle(title = group) }
 
-                val appEntry = list[it]
-                val summary = getSummary(option, appEntry.record) ?: { "" }
-                remember(appEntry) {
-                    AppListItemModel(appEntry.record, appEntry.label, summary)
-                }.AppItem()
+                    val appEntry = list[it]
+                    val summary = getSummary(option, appEntry.record) ?: { "" }
+                    remember(appEntry) {
+                            AppListItemModel(appEntry.record, appEntry.label, summary)
+                        }
+                        .AppItem()
+                }
             }
         }
     }
 }
 
-private fun <T : AppRecord> T.itemKey(option: Int) =
-    listOf(option, app.packageName, app.userId)
+private fun <T : AppRecord> T.itemKey(option: Int) = listOf(option, app.packageName, app.userId)
 
 /** Returns group title if this is the first item of the group. */
 private fun <T : AppRecord> AppListModel<T>.getGroupTitleIfFirst(
     option: Int,
     list: List<AppEntry<T>>,
     index: Int,
-): String? = getGroupTitle(option, list[index].record)?.takeIf {
-    index == 0 || it != getGroupTitle(option, list[index - 1].record)
-}
+): String? =
+    getGroupTitle(option, list[index].record)?.takeIf {
+        index == 0 || it != getGroupTitle(option, list[index - 1].record)
+    }
 
 @Composable
 private fun <T : AppRecord> rememberViewModel(
@@ -183,16 +201,19 @@
     viewModel.searchQuery.Sync(state.searchQuery)
 
     LifecycleEffect(onStart = { viewModel.reloadApps() })
-    val intentFilter = IntentFilter(Intent.ACTION_PACKAGE_ADDED).apply {
-        addAction(Intent.ACTION_PACKAGE_REMOVED)
-        addAction(Intent.ACTION_PACKAGE_CHANGED)
-        addDataScheme("package")
-    }
+    val intentFilter =
+        IntentFilter(Intent.ACTION_PACKAGE_ADDED).apply {
+            addAction(Intent.ACTION_PACKAGE_REMOVED)
+            addAction(Intent.ACTION_PACKAGE_CHANGED)
+            addDataScheme("package")
+        }
     for (userId in config.userIds) {
         DisposableBroadcastReceiverAsUser(
             intentFilter = intentFilter,
             userHandle = UserHandle.of(userId),
-        ) { viewModel.reloadApps() }
+        ) {
+            viewModel.reloadApps()
+        }
     }
     return viewModel
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index a87b815..216574a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -1,6 +1,7 @@
 package com.android.settingslib.bluetooth;
 
 import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.UNKNOWN_VALUE_PLACEHOLDER;
+import static com.android.settingslib.flags.Flags.audioSharingHysteresisModeFix;
 import static com.android.settingslib.widget.AdaptiveOutlineDrawable.ICON_TYPE_ADVANCED;
 
 import android.annotation.SuppressLint;
@@ -651,6 +652,13 @@
                 context.getContentResolver()));
     }
 
+    /** Returns if the le audio sharing hysteresis mode fix is available. */
+    @WorkerThread
+    public static boolean isAudioSharingHysteresisModeFixAvailable(@Nullable Context context) {
+        return (audioSharingHysteresisModeFix() && Flags.enableLeAudioSharing())
+                || (context != null && isAudioSharingPreviewEnabled(context.getContentResolver()));
+    }
+
     /** Returns if the le audio sharing is enabled. */
     public static boolean isAudioSharingEnabled() {
         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
@@ -733,13 +741,15 @@
     @WorkerThread
     public static boolean hasConnectedBroadcastSourceForBtDevice(
             @Nullable BluetoothDevice device, @Nullable LocalBluetoothManager localBtManager) {
-        if (Flags.audioSharingHysteresisModeFix()) {
+        if (localBtManager == null) {
+            Log.d(TAG, "Skip check hasConnectedBroadcastSourceForBtDevice due to arg is null");
+            return false;
+        }
+        if (isAudioSharingHysteresisModeFixAvailable(localBtManager.getContext())) {
             return hasActiveLocalBroadcastSourceForBtDevice(device, localBtManager);
         }
         LocalBluetoothLeBroadcastAssistant assistant =
-                localBtManager == null
-                        ? null
-                        : localBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
+                localBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
         if (device == null || assistant == null) {
             Log.d(TAG, "Skip check hasConnectedBroadcastSourceForBtDevice due to arg is null");
             return false;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index 6a9d568..dc52b4d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -18,6 +18,8 @@
 
 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
 
+import static com.android.settingslib.Utils.isAudioModeOngoingCall;
+
 import static java.util.stream.Collectors.toList;
 
 import android.annotation.CallbackExecutor;
@@ -54,7 +56,6 @@
 import androidx.annotation.RequiresApi;
 
 import com.android.settingslib.R;
-import com.android.settingslib.flags.Flags;
 
 import com.google.common.collect.ImmutableList;
 
@@ -303,6 +304,7 @@
                                         + ", sourceId = "
                                         + sourceId);
                     }
+                    updateFallbackActiveDeviceIfNeeded();
                 }
 
                 @Override
@@ -390,9 +392,6 @@
                                         + ", state = "
                                         + state);
                     }
-                    if (BluetoothUtils.isConnected(state)) {
-                        updateFallbackActiveDeviceIfNeeded();
-                    }
                 }
             };
 
@@ -1130,18 +1129,8 @@
             Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded for work profile.");
             return;
         }
-        if (mServiceBroadcast == null) {
-            Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to broadcast profile is null");
-            return;
-        }
-        List<BluetoothLeBroadcastMetadata> sources = mServiceBroadcast.getAllBroadcastMetadata();
-        if (sources.stream()
-                .noneMatch(source -> mServiceBroadcast.isPlaying(source.getBroadcastId()))) {
-            Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to no broadcast ongoing");
-            return;
-        }
-        if (mServiceBroadcastAssistant == null) {
-            Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to assistant profile is null");
+        if (isAudioModeOngoingCall(mContext)) {
+            Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to ongoing call");
             return;
         }
         Map<Integer, List<BluetoothDevice>> deviceGroupsInBroadcast = getDeviceGroupsInBroadcast();
@@ -1152,7 +1141,7 @@
         int targetGroupId = BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
         int fallbackActiveGroupId = BluetoothUtils.getPrimaryGroupIdForBroadcast(
                 mContext.getContentResolver());
-        if (Flags.audioSharingHysteresisModeFix()) {
+        if (BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext)) {
             int userPreferredPrimaryGroupId = getUserPreferredPrimaryGroupId();
             if (userPreferredPrimaryGroupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID
                     && deviceGroupsInBroadcast.containsKey(userPreferredPrimaryGroupId)) {
@@ -1193,7 +1182,8 @@
 
     @NonNull
     private Map<Integer, List<BluetoothDevice>> getDeviceGroupsInBroadcast() {
-        boolean hysteresisModeFixEnabled = Flags.audioSharingHysteresisModeFix();
+        boolean hysteresisModeFixEnabled =
+                BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext);
         List<BluetoothDevice> connectedDevices = mServiceBroadcastAssistant.getConnectedDevices();
         return connectedDevices.stream()
                 .filter(
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistantCallbackExt.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistantCallbackExt.kt
index 91a99ae..a0a6d26 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistantCallbackExt.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistantCallbackExt.kt
@@ -35,11 +35,7 @@
                     sink: BluetoothDevice,
                     sourceId: Int,
                     state: BluetoothLeBroadcastReceiveState
-                ) {
-                    if (BluetoothUtils.isConnected(state)) {
-                        launch { send(Unit) }
-                    }
-                }
+                ) {}
 
                 override fun onSourceRemoved(sink: BluetoothDevice, sourceId: Int, reason: Int) {
                     launch { send(Unit) }
@@ -55,7 +51,9 @@
 
                 override fun onSourceFound(source: BluetoothLeBroadcastMetadata) {}
 
-                override fun onSourceAdded(sink: BluetoothDevice, sourceId: Int, reason: Int) {}
+                override fun onSourceAdded(sink: BluetoothDevice, sourceId: Int, reason: Int) {
+                    launch { send(Unit) }
+                }
 
                 override fun onSourceAddFailed(
                     sink: BluetoothDevice,
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
index 3cc111f..34d3bd9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
@@ -24,7 +24,6 @@
 import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
 import static android.service.notification.SystemZenRules.getTriggerDescriptionForScheduleEvent;
 import static android.service.notification.SystemZenRules.getTriggerDescriptionForScheduleTime;
-import static android.service.notification.ZenModeConfig.tryParseCountdownConditionId;
 import static android.service.notification.ZenModeConfig.tryParseEventConditionId;
 import static android.service.notification.ZenModeConfig.tryParseScheduleConditionId;
 
@@ -225,27 +224,6 @@
     }
 
     /**
-     * Returns a "dynamic" trigger description. For some modes (such as manual Do Not Disturb)
-     * when activated, we know when (and if) the mode is expected to end on its own; this dynamic
-     * description reflects that. In other cases, returns {@link #getTriggerDescription}.
-     */
-    @Nullable
-    public String getDynamicDescription(Context context) {
-        if (isManualDnd() && isActive()) {
-            long countdownEndTime = tryParseCountdownConditionId(mRule.getConditionId());
-            if (countdownEndTime > 0) {
-                CharSequence formattedTime = ZenModeConfig.getFormattedTime(context,
-                        countdownEndTime, ZenModeConfig.isToday(countdownEndTime),
-                        context.getUserId());
-                return context.getString(com.android.internal.R.string.zen_mode_until,
-                        formattedTime);
-            }
-        }
-
-        return getTriggerDescription();
-    }
-
-    /**
      * Returns the {@link ZenIcon.Key} corresponding to the icon resource for this mode. This can be
      * either app-provided (via {@link AutomaticZenRule#setIconResId}, user-chosen (via the icon
      * picker in Settings), or a default icon based on the mode {@link Kind} and {@link #getType}.
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModeDescriptions.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModeDescriptions.java
new file mode 100644
index 0000000..f577698
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModeDescriptions.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.notification.modes;
+
+import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME;
+import static android.service.notification.ZenModeConfig.tryParseCountdownConditionId;
+
+import android.content.Context;
+import android.service.notification.SystemZenRules;
+import android.service.notification.ZenModeConfig;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.google.common.base.Strings;
+
+public final class ZenModeDescriptions {
+
+    private final Context mContext;
+
+    public ZenModeDescriptions(@NonNull Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Returns a version of the mode's trigger description that might be "dynamic".
+     *
+     * <p>For some modes (such as manual Do Not Disturb) when activated, we know when (and if) the
+     * mode is expected to end on its own; this description reflects that. In other cases,
+     * returns {@link ZenMode#getTriggerDescription}.
+     */
+    @Nullable
+    public String getTriggerDescription(@NonNull ZenMode mode) {
+        if (mode.isManualDnd() && mode.isActive()) {
+            long countdownEndTime = tryParseCountdownConditionId(mode.getRule().getConditionId());
+            if (countdownEndTime > 0) {
+                CharSequence formattedTime = ZenModeConfig.getFormattedTime(mContext,
+                        countdownEndTime, ZenModeConfig.isToday(countdownEndTime),
+                        mContext.getUserId());
+                return mContext.getString(com.android.internal.R.string.zen_mode_until,
+                        formattedTime);
+            }
+        }
+
+        return Strings.emptyToNull(mode.getTriggerDescription());
+    }
+
+    /**
+     * Returns a version of the {@link ZenMode} trigger description that is suitable for
+     * accessibility (for example, where abbreviations are expanded to full words).
+     *
+     * <p>Returns {@code null} If the standard trigger description (returned by
+     * {@link #getTriggerDescription}) is sufficient.
+     */
+    @Nullable
+    public String getTriggerDescriptionForAccessibility(@NonNull ZenMode mode) {
+        // Only one special case: time-based schedules, where we want to use full day names.
+        if (mode.isSystemOwned() && mode.getType() == TYPE_SCHEDULE_TIME) {
+            ZenModeConfig.ScheduleInfo schedule = ZenModeConfig.tryParseScheduleConditionId(
+                    mode.getRule().getConditionId());
+            if (schedule != null) {
+                String fullDaysSummary = SystemZenRules.getDaysOfWeekFull(mContext, schedule);
+                if (fullDaysSummary != null) {
+                    return fullDaysSummary + ", " + SystemZenRules.getTimeSummary(mContext,
+                            schedule);
+                }
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index 6d481db..fa5d542 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -1256,4 +1256,40 @@
 
         assertThat(BluetoothUtils.isAudioSharingUIAvailable(mContext)).isTrue();
     }
+
+    @Test
+    public void isAudioSharingHysteresisModeFixAvailable_mainAndPreviewFlagOff_returnsFalse() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+        mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION);
+
+        assertThat(BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext)).isFalse();
+    }
+
+    @Test
+    public void isAudioSharingHysteresisModeFixAvailable_hysteresisFixFlagOff_returnsFalse() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+        mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION);
+
+        assertThat(BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext)).isFalse();
+    }
+
+    @Test
+    public void isAudioSharingHysteresisModeFixAvailable_previewFlagOn_returnsTrue() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+        mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION);
+
+        assertThat(BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext)).isTrue();
+    }
+
+    @Test
+    public void isAudioSharingHysteresisModeFixAvailable_mainAndPreviewFlagOn_returnsTrue() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+        mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION);
+
+        assertThat(BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(mContext)).isTrue();
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeDescriptionsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeDescriptionsTest.java
new file mode 100644
index 0000000..2b3accd
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeDescriptionsTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.notification.modes;
+
+import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME;
+import static android.service.notification.SystemZenRules.PACKAGE_ANDROID;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.service.notification.ZenModeConfig;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.Calendar;
+
+@RunWith(RobolectricTestRunner.class)
+public class ZenModeDescriptionsTest {
+
+    private final ZenModeDescriptions mDescriptions = new ZenModeDescriptions(
+            RuntimeEnvironment.getApplication());
+
+    @Test
+    public void getTriggerDescriptionForAccessibility_scheduleTime_usesFullDays() {
+        ZenModeConfig.ScheduleInfo scheduleInfo = new ZenModeConfig.ScheduleInfo();
+        scheduleInfo.days = new int[] { Calendar.MONDAY };
+        scheduleInfo.startHour = 11;
+        scheduleInfo.endHour = 15;
+        ZenMode mode = new TestModeBuilder()
+                .setPackage(PACKAGE_ANDROID)
+                .setType(TYPE_SCHEDULE_TIME)
+                .setConditionId(ZenModeConfig.toScheduleConditionId(scheduleInfo))
+                .build();
+
+        assertThat(mDescriptions.getTriggerDescriptionForAccessibility(mode))
+                .isEqualTo("Monday, 11:00 AM - 3:00 PM");
+    }
+
+    @Test
+    public void getTriggerDescriptionForAccessibility_otherMode_isNull() {
+        ZenMode mode = new TestModeBuilder().setTriggerDescription("When December ends").build();
+        assertThat(mDescriptions.getTriggerDescriptionForAccessibility(mode)).isNull();
+    }
+}
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 bffda8b..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",
     ],
@@ -497,6 +481,9 @@
     resource_dirs: [],
     static_libs: [
         "//frameworks/libs/systemui:compilelib",
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/dagger:api",
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/util/settings:api",
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail:impl",
         "SystemUI-res",
         "WifiTrackerLib",
         "WindowManager-Shell",
@@ -770,6 +757,9 @@
     ],
     static_libs: [
         "//frameworks/libs/systemui:compilelib",
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/dagger:api",
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/util/settings:api",
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail:impl",
         "SystemUI-tests-base",
         "androidx.test.uiautomator_uiautomator",
         "androidx.core_core-animation-testing",
@@ -910,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/common/Android.bp b/packages/SystemUI/common/Android.bp
index 91dc3e3..9f15983 100644
--- a/packages/SystemUI/common/Android.bp
+++ b/packages/SystemUI/common/Android.bp
@@ -30,5 +30,9 @@
         "src/**/*.kt",
     ],
 
+    static_libs: ["SystemUI-shared-utils"],
+
+    libs: ["//frameworks/libs/systemui:tracinglib-platform"],
+
     kotlincflags: ["-Xjvm-default=all"],
 }
diff --git a/packages/SystemUI/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt b/packages/SystemUI/common/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt
rename to packages/SystemUI/common/src/com/android/systemui/common/coroutine/ConflatedCallbackFlow.kt
diff --git a/packages/SystemUI/src/com/android/systemui/coroutines/Tracing.kt b/packages/SystemUI/common/src/com/android/systemui/coroutines/Tracing.kt
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/coroutines/Tracing.kt
rename to packages/SystemUI/common/src/com/android/systemui/coroutines/Tracing.kt
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/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index d976e8e..44f60cb 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -50,6 +50,8 @@
 import androidx.compose.ui.util.lerp
 import com.android.compose.animation.scene.content.Content
 import com.android.compose.animation.scene.content.state.TransitionState
+import com.android.compose.animation.scene.transformation.CustomPropertyTransformation
+import com.android.compose.animation.scene.transformation.InterpolatedPropertyTransformation
 import com.android.compose.animation.scene.transformation.PropertyTransformation
 import com.android.compose.animation.scene.transformation.SharedElementTransformation
 import com.android.compose.animation.scene.transformation.TransformationWithRange
@@ -1308,7 +1310,14 @@
                 checkNotNull(if (currentContent == toContent) toState else fromState)
             val idleValue = contentValue(overscrollState)
             val targetValue =
-                with(propertySpec.transformation) {
+                with(
+                    propertySpec.transformation.requireInterpolatedTransformation(
+                        element,
+                        transition,
+                    ) {
+                        "Custom transformations in overscroll specs should not be possible"
+                    }
+                ) {
                     layoutImpl.propertyTransformationScope.transform(
                         currentContent,
                         element.key,
@@ -1390,7 +1399,7 @@
     // fromContent or toContent during interruptions.
     val content = contentState.content
 
-    val transformation =
+    val transformationWithRange =
         transformation(transition.transformationSpec.transformations(element.key, content))
 
     val previewTransformation =
@@ -1403,7 +1412,14 @@
         val idleValue = contentValue(contentState)
         val isEntering = content == toContent
         val previewTargetValue =
-            with(previewTransformation.transformation) {
+            with(
+                previewTransformation.transformation.requireInterpolatedTransformation(
+                    element,
+                    transition,
+                ) {
+                    "Custom transformations in preview specs should not be possible"
+                }
+            ) {
                 layoutImpl.propertyTransformationScope.transform(
                     content,
                     element.key,
@@ -1413,8 +1429,15 @@
             }
 
         val targetValueOrNull =
-            transformation?.let { transformation ->
-                with(transformation.transformation) {
+            transformationWithRange?.let { transformation ->
+                with(
+                    transformation.transformation.requireInterpolatedTransformation(
+                        element,
+                        transition,
+                    ) {
+                        "Custom transformations are not allowed for properties with a preview"
+                    }
+                ) {
                     layoutImpl.propertyTransformationScope.transform(
                         content,
                         element.key,
@@ -1461,7 +1484,7 @@
             lerp(
                 lerp(previewTargetValue, targetValueOrNull ?: idleValue, previewRangeProgress),
                 idleValue,
-                transformation?.range?.progress(transition.progress) ?: transition.progress,
+                transformationWithRange?.range?.progress(transition.progress) ?: transition.progress,
             )
         } else {
             if (targetValueOrNull == null) {
@@ -1474,22 +1497,39 @@
                 lerp(
                     lerp(idleValue, previewTargetValue, previewRangeProgress),
                     targetValueOrNull,
-                    transformation.range?.progress(transition.progress) ?: transition.progress,
+                    transformationWithRange.range?.progress(transition.progress)
+                        ?: transition.progress,
                 )
             }
         }
     }
 
-    if (transformation == null) {
+    if (transformationWithRange == null) {
         // If there is no transformation explicitly associated to this element value, let's use
         // the value given by the system (like the current position and size given by the layout
         // pass).
         return currentValue()
     }
 
+    val transformation = transformationWithRange.transformation
+    when (transformation) {
+        is CustomPropertyTransformation ->
+            return with(transformation) {
+                layoutImpl.propertyTransformationScope.transform(
+                    content,
+                    element.key,
+                    transition,
+                    transition.coroutineScope,
+                )
+            }
+        is InterpolatedPropertyTransformation -> {
+            /* continue */
+        }
+    }
+
     val idleValue = contentValue(contentState)
     val targetValue =
-        with(transformation.transformation) {
+        with(transformation) {
             layoutImpl.propertyTransformationScope.transform(
                 content,
                 element.key,
@@ -1506,7 +1546,7 @@
 
     val progress = transition.progress
     // TODO(b/290184746): Make sure that we don't overflow transformations associated to a range.
-    val rangeProgress = transformation.range?.progress(progress) ?: progress
+    val rangeProgress = transformationWithRange.range?.progress(progress) ?: progress
 
     // Interpolate between the value at rest and the value before entering/after leaving.
     val isEntering =
@@ -1523,6 +1563,22 @@
     }
 }
 
+private inline fun <T> PropertyTransformation<T>.requireInterpolatedTransformation(
+    element: Element,
+    transition: TransitionState.Transition,
+    errorMessage: () -> String,
+): InterpolatedPropertyTransformation<T> {
+    return when (this) {
+        is InterpolatedPropertyTransformation -> this
+        is CustomPropertyTransformation -> {
+            val elem = element.key.debugName
+            val fromContent = transition.fromContent
+            val toContent = transition.toContent
+            error("${errorMessage()} (element=$elem fromContent=$fromContent toContent=$toContent)")
+        }
+    }
+}
+
 private inline fun <T> interpolateSharedElement(
     transition: TransitionState.Transition,
     contentValue: (Element.State) -> T,
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/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 72b29ee..7eb5a3f 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -32,6 +32,7 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.CoroutineStart
 import kotlinx.coroutines.Job
+import kotlinx.coroutines.cancel
 import kotlinx.coroutines.launch
 
 /**
@@ -466,9 +467,9 @@
             return
         }
 
-        // Make sure that this transition settles in case it was force finished, for instance by
-        // calling snapToScene().
-        transition.freezeAndAnimateToCurrentState()
+        // Make sure that this transition is cancelled in case it was force finished, for instance
+        // if snapToScene() is called.
+        transition.coroutineScope.cancel()
 
         val transitionStates = this.transitionStates
         if (!transitionStates.contains(transition)) {
@@ -550,8 +551,8 @@
         }
 
         val shouldSnap =
-            (isProgressCloseTo(0f) && transition.currentScene == transition.fromContent) ||
-                (isProgressCloseTo(1f) && transition.currentScene == transition.toContent)
+            (isProgressCloseTo(0f) && transition.isFromCurrentContent()) ||
+                (isProgressCloseTo(1f) && transition.isToCurrentContent())
         return if (shouldSnap) {
             finishAllTransitions()
             true
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index b083f79..569593c 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -26,18 +26,18 @@
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.util.fastForEach
 import com.android.compose.animation.scene.content.state.TransitionState
-import com.android.compose.animation.scene.transformation.AnchoredSize
-import com.android.compose.animation.scene.transformation.AnchoredTranslate
-import com.android.compose.animation.scene.transformation.DrawScale
-import com.android.compose.animation.scene.transformation.EdgeTranslate
-import com.android.compose.animation.scene.transformation.Fade
-import com.android.compose.animation.scene.transformation.OverscrollTranslate
+import com.android.compose.animation.scene.transformation.CustomAlphaTransformation
+import com.android.compose.animation.scene.transformation.CustomOffsetTransformation
+import com.android.compose.animation.scene.transformation.CustomScaleTransformation
+import com.android.compose.animation.scene.transformation.CustomSizeTransformation
+import com.android.compose.animation.scene.transformation.InterpolatedAlphaTransformation
+import com.android.compose.animation.scene.transformation.InterpolatedOffsetTransformation
+import com.android.compose.animation.scene.transformation.InterpolatedScaleTransformation
+import com.android.compose.animation.scene.transformation.InterpolatedSizeTransformation
 import com.android.compose.animation.scene.transformation.PropertyTransformation
-import com.android.compose.animation.scene.transformation.ScaleSize
 import com.android.compose.animation.scene.transformation.SharedElementTransformation
 import com.android.compose.animation.scene.transformation.Transformation
 import com.android.compose.animation.scene.transformation.TransformationWithRange
-import com.android.compose.animation.scene.transformation.Translate
 
 /** The transitions configuration of a [SceneTransitionLayout]. */
 class SceneTransitions
@@ -359,35 +359,34 @@
                         transformationWithRange
                             as TransformationWithRange<SharedElementTransformation>
                 }
-                is Translate,
-                is OverscrollTranslate,
-                is EdgeTranslate,
-                is AnchoredTranslate -> {
+                is InterpolatedOffsetTransformation,
+                is CustomOffsetTransformation -> {
                     throwIfNotNull(offset, element, name = "offset")
                     offset =
                         transformationWithRange
                             as TransformationWithRange<PropertyTransformation<Offset>>
                 }
-                is ScaleSize,
-                is AnchoredSize -> {
+                is InterpolatedSizeTransformation,
+                is CustomSizeTransformation -> {
                     throwIfNotNull(size, element, name = "size")
                     size =
                         transformationWithRange
                             as TransformationWithRange<PropertyTransformation<IntSize>>
                 }
-                is DrawScale -> {
+                is InterpolatedScaleTransformation,
+                is CustomScaleTransformation -> {
                     throwIfNotNull(drawScale, element, name = "drawScale")
                     drawScale =
                         transformationWithRange
                             as TransformationWithRange<PropertyTransformation<Scale>>
                 }
-                is Fade -> {
+                is InterpolatedAlphaTransformation,
+                is CustomAlphaTransformation -> {
                     throwIfNotNull(alpha, element, name = "alpha")
                     alpha =
                         transformationWithRange
                             as TransformationWithRange<PropertyTransformation<Float>>
                 }
-                else -> error("Unknown transformation: $transformation")
             }
         }
 
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/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index dc26b6b..1fdfca9 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -26,6 +26,7 @@
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.scene.content.state.TransitionState
+import com.android.compose.animation.scene.transformation.CustomPropertyTransformation
 import kotlin.math.tanh
 
 /** Define the [transitions][SceneTransitions] to be used with a [SceneTransitionLayout]. */
@@ -527,6 +528,16 @@
         anchorWidth: Boolean = true,
         anchorHeight: Boolean = true,
     )
+
+    /**
+     * Apply a [CustomPropertyTransformation] to one or more elements.
+     *
+     * @see com.android.compose.animation.scene.transformation.CustomSizeTransformation
+     * @see com.android.compose.animation.scene.transformation.CustomOffsetTransformation
+     * @see com.android.compose.animation.scene.transformation.CustomAlphaTransformation
+     * @see com.android.compose.animation.scene.transformation.CustomScaleTransformation
+     */
+    fun transformation(transformation: CustomPropertyTransformation<*>)
 }
 
 /** This converter lets you change a linear progress into a function of your choice. */
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index e461f9c..79f8cd4 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -30,6 +30,7 @@
 import com.android.compose.animation.scene.content.state.TransitionState
 import com.android.compose.animation.scene.transformation.AnchoredSize
 import com.android.compose.animation.scene.transformation.AnchoredTranslate
+import com.android.compose.animation.scene.transformation.CustomPropertyTransformation
 import com.android.compose.animation.scene.transformation.DrawScale
 import com.android.compose.animation.scene.transformation.EdgeTranslate
 import com.android.compose.animation.scene.transformation.Fade
@@ -173,7 +174,7 @@
         range = null
     }
 
-    protected fun transformation(transformation: Transformation) {
+    protected fun addTransformation(transformation: Transformation) {
         val transformationWithRange = TransformationWithRange(transformation, range)
         transformations.add(
             if (reversed) {
@@ -185,11 +186,11 @@
     }
 
     override fun fade(matcher: ElementMatcher) {
-        transformation(Fade(matcher))
+        addTransformation(Fade(matcher))
     }
 
     override fun translate(matcher: ElementMatcher, x: Dp, y: Dp) {
-        transformation(Translate(matcher, x, y))
+        addTransformation(Translate(matcher, x, y))
     }
 
     override fun translate(
@@ -197,19 +198,19 @@
         edge: Edge,
         startsOutsideLayoutBounds: Boolean,
     ) {
-        transformation(EdgeTranslate(matcher, edge, startsOutsideLayoutBounds))
+        addTransformation(EdgeTranslate(matcher, edge, startsOutsideLayoutBounds))
     }
 
     override fun anchoredTranslate(matcher: ElementMatcher, anchor: ElementKey) {
-        transformation(AnchoredTranslate(matcher, anchor))
+        addTransformation(AnchoredTranslate(matcher, anchor))
     }
 
     override fun scaleSize(matcher: ElementMatcher, width: Float, height: Float) {
-        transformation(ScaleSize(matcher, width, height))
+        addTransformation(ScaleSize(matcher, width, height))
     }
 
     override fun scaleDraw(matcher: ElementMatcher, scaleX: Float, scaleY: Float, pivot: Offset) {
-        transformation(DrawScale(matcher, scaleX, scaleY, pivot))
+        addTransformation(DrawScale(matcher, scaleX, scaleY, pivot))
     }
 
     override fun anchoredSize(
@@ -218,7 +219,12 @@
         anchorWidth: Boolean,
         anchorHeight: Boolean,
     ) {
-        transformation(AnchoredSize(matcher, anchor, anchorWidth, anchorHeight))
+        addTransformation(AnchoredSize(matcher, anchor, anchorWidth, anchorHeight))
+    }
+
+    override fun transformation(transformation: CustomPropertyTransformation<*>) {
+        check(range == null) { "Custom transformations can not be applied inside a range" }
+        addTransformation(transformation)
     }
 }
 
@@ -257,7 +263,7 @@
                 "(${transition.toContent.debugName})"
         }
 
-        transformation(SharedElementTransformation(matcher, enabled, elevateInContent))
+        addTransformation(SharedElementTransformation(matcher, enabled, elevateInContent))
     }
 
     override fun timestampRange(
@@ -288,6 +294,6 @@
         x: OverscrollScope.() -> Float,
         y: OverscrollScope.() -> Float,
     ) {
-        transformation(OverscrollTranslate(matcher, x, y))
+        addTransformation(OverscrollTranslate(matcher, x, y))
     }
 }
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/src/com/android/compose/animation/scene/content/state/TransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
index e3118d67..38b1aaa 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/state/TransitionState.kt
@@ -35,6 +35,8 @@
 import com.android.compose.animation.scene.TransformationSpec
 import com.android.compose.animation.scene.TransformationSpecImpl
 import com.android.compose.animation.scene.TransitionKey
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.launch
 
 /** The state associated to a [SceneTransitionLayout] at some specific point in time. */
@@ -128,7 +130,7 @@
              * starting a swipe transition to show [overlay] and will be `true` only once the swipe
              * transition is committed.
              */
-            protected abstract val isEffectivelyShown: Boolean
+            abstract val isEffectivelyShown: Boolean
 
             init {
                 check(
@@ -163,7 +165,7 @@
              * [fromOverlay] by [toOverlay] and will [toOverlay] once the swipe transition is
              * committed.
              */
-            protected abstract val effectivelyShownOverlay: OverlayKey
+            abstract val effectivelyShownOverlay: OverlayKey
 
             init {
                 check(fromOverlay != toOverlay)
@@ -279,8 +281,24 @@
          */
         private var interruptionDecay: Animatable<Float, AnimationVector1D>? = null
 
-        /** Whether this transition was already started. */
-        private var wasStarted = false
+        /**
+         * The coroutine scope associated to this transition.
+         *
+         * This coroutine scope can be used to launch animations associated to this transition,
+         * which will not finish until at least one animation/job is still running in the scope.
+         *
+         * Important: Make sure to never launch long-running jobs in this scope, otherwise the
+         * transition will never be considered as finished.
+         */
+        internal val coroutineScope: CoroutineScope
+            get() =
+                _coroutineScope
+                    ?: error(
+                        "Transition.coroutineScope can only be accessed once the transition was " +
+                            "started "
+                    )
+
+        private var _coroutineScope: CoroutineScope? = null
 
         init {
             check(fromContent != toContent)
@@ -326,6 +344,21 @@
             }
         }
 
+        /** Whether [fromContent] is effectively the current content of the transition. */
+        internal fun isFromCurrentContent() = isCurrentContent(expectedFrom = true)
+
+        /** Whether [toContent] is effectively the current content of the transition. */
+        internal fun isToCurrentContent() = isCurrentContent(expectedFrom = false)
+
+        private fun isCurrentContent(expectedFrom: Boolean): Boolean {
+            val expectedContent = if (expectedFrom) fromContent else toContent
+            return when (this) {
+                is ChangeScene -> currentScene == expectedContent
+                is ReplaceOverlay -> effectivelyShownOverlay == expectedContent
+                is ShowOrHideOverlay -> isEffectivelyShown == (expectedContent == overlay)
+            }
+        }
+
         /** Run this transition and return once it is finished. */
         protected abstract suspend fun run()
 
@@ -341,10 +374,11 @@
         abstract fun freezeAndAnimateToCurrentState()
 
         internal suspend fun runInternal() {
-            check(!wasStarted) { "A Transition can be started only once." }
-            wasStarted = true
-
-            run()
+            check(_coroutineScope == null) { "A Transition can be started only once." }
+            coroutineScope {
+                _coroutineScope = this
+                run()
+            }
         }
 
         internal fun updateOverscrollSpecs(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
index 0ddeb7c..85bb533 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
@@ -28,7 +28,7 @@
     private val anchor: ElementKey,
     private val anchorWidth: Boolean,
     private val anchorHeight: Boolean,
-) : PropertyTransformation<IntSize> {
+) : InterpolatedSizeTransformation {
     override fun PropertyTransformationScope.transform(
         content: ContentKey,
         element: ElementKey,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
index 47508b4..04cd683 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
@@ -26,7 +26,7 @@
 internal class AnchoredTranslate(
     override val matcher: ElementMatcher,
     private val anchor: ElementKey,
-) : PropertyTransformation<Offset> {
+) : InterpolatedOffsetTransformation {
     override fun PropertyTransformationScope.transform(
         content: ContentKey,
         element: ElementKey,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt
index 8488ae5..45d6d40 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt
@@ -32,7 +32,7 @@
     private val scaleX: Float,
     private val scaleY: Float,
     private val pivot: Offset = Offset.Unspecified,
-) : PropertyTransformation<Scale> {
+) : InterpolatedScaleTransformation {
     override fun PropertyTransformationScope.transform(
         content: ContentKey,
         element: ElementKey,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
index 884aae4b..21d66d7 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
@@ -28,7 +28,7 @@
     override val matcher: ElementMatcher,
     private val edge: Edge,
     private val startsOutsideLayoutBounds: Boolean = true,
-) : PropertyTransformation<Offset> {
+) : InterpolatedOffsetTransformation {
     override fun PropertyTransformationScope.transform(
         content: ContentKey,
         element: ElementKey,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt
index ef769e7..d942273 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt
@@ -22,7 +22,7 @@
 import com.android.compose.animation.scene.content.state.TransitionState
 
 /** Fade an element in or out. */
-internal class Fade(override val matcher: ElementMatcher) : PropertyTransformation<Float> {
+internal class Fade(override val matcher: ElementMatcher) : InterpolatedAlphaTransformation {
     override fun PropertyTransformationScope.transform(
         content: ContentKey,
         element: ElementKey,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
index ef3654b..5f3cdab 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
@@ -31,7 +31,7 @@
     override val matcher: ElementMatcher,
     private val width: Float = 1f,
     private val height: Float = 1f,
-) : PropertyTransformation<IntSize> {
+) : InterpolatedSizeTransformation {
     override fun PropertyTransformationScope.transform(
         content: ContentKey,
         element: ElementKey,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
index 74a3ead..d5143d7 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
@@ -18,7 +18,9 @@
 
 import androidx.compose.animation.core.Easing
 import androidx.compose.animation.core.LinearEasing
+import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.util.fastCoerceAtLeast
 import androidx.compose.ui.util.fastCoerceAtMost
@@ -27,7 +29,9 @@
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.ElementMatcher
 import com.android.compose.animation.scene.ElementStateScope
+import com.android.compose.animation.scene.Scale
 import com.android.compose.animation.scene.content.state.TransitionState
+import kotlinx.coroutines.CoroutineScope
 
 /** A transformation applied to one or more elements during a transition. */
 sealed interface Transformation {
@@ -35,12 +39,6 @@
      * The matcher that should match the element(s) to which this transformation should be applied.
      */
     val matcher: ElementMatcher
-
-    /*
-     * Reverse this transformation. This is called when we use Transition(from = A, to = B) when
-     * animating from B to A and there is no Transition(from = B, to = A) defined.
-     */
-    fun reversed(): Transformation = this
 }
 
 internal class SharedElementTransformation(
@@ -50,7 +48,13 @@
 ) : Transformation
 
 /** A transformation that changes the value of an element property, like its size or offset. */
-interface PropertyTransformation<T> : Transformation {
+sealed interface PropertyTransformation<T> : Transformation
+
+/**
+ * A transformation to a target/transformed value that is automatically interpolated using the
+ * transition progress and transformation range.
+ */
+sealed interface InterpolatedPropertyTransformation<T> : PropertyTransformation<T> {
     /**
      * Return the transformed value for the given property, i.e.:
      * - the value at progress = 0% for elements that are entering the layout (i.e. elements in the
@@ -58,8 +62,8 @@
      * - the value at progress = 100% for elements that are leaving the layout (i.e. elements in the
      *   content we are transitioning from).
      *
-     * The returned value will be interpolated using the [transition] progress and [idleValue], the
-     * value of the property when we are idle.
+     * The returned value will be automatically interpolated using the [transition] progress, the
+     * transformation range and [idleValue], the value of the property when we are idle.
      */
     fun PropertyTransformationScope.transform(
         content: ContentKey,
@@ -69,6 +73,50 @@
     ): T
 }
 
+/** An [InterpolatedPropertyTransformation] applied to the size of one or more elements. */
+interface InterpolatedSizeTransformation : InterpolatedPropertyTransformation<IntSize>
+
+/** An [InterpolatedPropertyTransformation] applied to the offset of one or more elements. */
+interface InterpolatedOffsetTransformation : InterpolatedPropertyTransformation<Offset>
+
+/** An [InterpolatedPropertyTransformation] applied to the alpha of one or more elements. */
+interface InterpolatedAlphaTransformation : InterpolatedPropertyTransformation<Float>
+
+/** An [InterpolatedPropertyTransformation] applied to the scale of one or more elements. */
+interface InterpolatedScaleTransformation : InterpolatedPropertyTransformation<Scale>
+
+sealed interface CustomPropertyTransformation<T> : PropertyTransformation<T> {
+    /**
+     * Return the value that the property should have in the current frame for the given [content]
+     * and [element].
+     *
+     * This transformation can use [transitionScope] to launch animations associated to
+     * [transition], which will not finish until at least one animation/job is still running in the
+     * scope.
+     *
+     * Important: Make sure to never launch long-running jobs in [transitionScope], otherwise
+     * [transition] will never be considered as finished.
+     */
+    fun PropertyTransformationScope.transform(
+        content: ContentKey,
+        element: ElementKey,
+        transition: TransitionState.Transition,
+        transitionScope: CoroutineScope,
+    ): T
+}
+
+/** A [CustomPropertyTransformation] applied to the size of one or more elements. */
+interface CustomSizeTransformation : CustomPropertyTransformation<IntSize>
+
+/** A [CustomPropertyTransformation] applied to the offset of one or more elements. */
+interface CustomOffsetTransformation : CustomPropertyTransformation<Offset>
+
+/** A [CustomPropertyTransformation] applied to the alpha of one or more elements. */
+interface CustomAlphaTransformation : CustomPropertyTransformation<Float>
+
+/** A [CustomPropertyTransformation] applied to the scale of one or more elements. */
+interface CustomScaleTransformation : CustomPropertyTransformation<Scale>
+
 interface PropertyTransformationScope : Density, ElementStateScope {
     /** The current [direction][LayoutDirection] of the layout. */
     val layoutDirection: LayoutDirection
@@ -101,7 +149,7 @@
     }
 
     /** Reverse this range. */
-    fun reversed() =
+    internal fun reversed() =
         TransformationRange(start = reverseBound(end), end = reverseBound(start), easing = easing)
 
     /** Get the progress of this range given the global [transitionProgress]. */
@@ -128,6 +176,6 @@
     }
 
     companion object {
-        const val BoundUnspecified = Float.MIN_VALUE
+        internal const val BoundUnspecified = Float.MIN_VALUE
     }
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
index 356ed99..d756c86 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
@@ -30,7 +30,7 @@
     override val matcher: ElementMatcher,
     private val x: Dp = 0.dp,
     private val y: Dp = 0.dp,
-) : PropertyTransformation<Offset> {
+) : InterpolatedOffsetTransformation {
     override fun PropertyTransformationScope.transform(
         content: ContentKey,
         element: ElementKey,
@@ -45,7 +45,7 @@
     override val matcher: ElementMatcher,
     val x: OverscrollScope.() -> Float = { 0f },
     val y: OverscrollScope.() -> Float = { 0f },
-) : PropertyTransformation<Offset> {
+) : InterpolatedOffsetTransformation {
     private val cachedOverscrollScope = CachedOverscrollScope()
 
     override fun PropertyTransformationScope.transform(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
index 20a0b39..3f18236 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
@@ -365,12 +365,16 @@
         flingBehavior: FlingBehavior,
     ): Float {
         return with(flingBehavior) {
-            object : ScrollScope {
-                    override fun scrollBy(pixels: Float): Float {
-                        return controller.onScroll(pixels, NestedScrollSource.SideEffect)
+            val remainingVelocity =
+                object : ScrollScope {
+                        override fun scrollBy(pixels: Float): Float {
+                            return controller.onScroll(pixels, NestedScrollSource.SideEffect)
+                        }
                     }
-                }
-                .performFling(initialVelocity)
+                    .performFling(initialVelocity)
+
+            // returns the consumed velocity
+            initialVelocity - remainingVelocity
         }
     }
 }
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/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index 2b70908..57ad81e 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -23,6 +23,7 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.TestOverlays.OverlayA
 import com.android.compose.animation.scene.TestScenes.SceneA
 import com.android.compose.animation.scene.TestScenes.SceneB
 import com.android.compose.animation.scene.TestScenes.SceneC
@@ -35,12 +36,15 @@
 import com.android.compose.test.transition
 import com.google.common.truth.Truth.assertThat
 import kotlin.coroutines.cancellation.CancellationException
+import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.cancelAndJoin
 import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.flow.consumeAsFlow
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Assert.assertThrows
 import org.junit.Rule
@@ -185,6 +189,25 @@
     }
 
     @Test
+    fun snapToIdleIfClose_snapToStart_overlays() = runMonotonicClockTest {
+        val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
+        state.startTransitionImmediately(
+            animationScope = backgroundScope,
+            transition(SceneA, OverlayA, isEffectivelyShown = { false }, progress = { 0.2f }),
+        )
+        assertThat(state.isTransitioning()).isTrue()
+
+        // Ignore the request if the progress is not close to 0 or 1, using the threshold.
+        assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
+        assertThat(state.isTransitioning()).isTrue()
+
+        // Go to the initial scene if it is close to 0.
+        assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue()
+        assertThat(state.isTransitioning()).isFalse()
+        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneA))
+    }
+
+    @Test
     fun snapToIdleIfClose_snapToEnd() = runMonotonicClockTest {
         val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
         state.startTransitionImmediately(
@@ -204,6 +227,25 @@
     }
 
     @Test
+    fun snapToIdleIfClose_snapToEnd_overlays() = runMonotonicClockTest {
+        val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
+        state.startTransitionImmediately(
+            animationScope = backgroundScope,
+            transition(SceneA, OverlayA, isEffectivelyShown = { true }, progress = { 0.8f }),
+        )
+        assertThat(state.isTransitioning()).isTrue()
+
+        // Ignore the request if the progress is not close to 0 or 1, using the threshold.
+        assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
+        assertThat(state.isTransitioning()).isTrue()
+
+        // Go to the final scene if it is close to 1.
+        assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue()
+        assertThat(state.isTransitioning()).isFalse()
+        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneA, setOf(OverlayA)))
+    }
+
+    @Test
     fun snapToIdleIfClose_multipleTransitions() = runMonotonicClockTest {
         val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
 
@@ -601,4 +643,47 @@
             runBlocking { state.startTransition(transition) }
         }
     }
+
+    @Test
+    fun transitionFinishedWhenScopeIsEmpty() = runTest {
+        val state = MutableSceneTransitionLayoutState(SceneA)
+
+        // Start a transition.
+        val transition = transition(from = SceneA, to = SceneB)
+        state.startTransitionImmediately(backgroundScope, transition)
+        assertThat(state.transitionState).isSceneTransition()
+
+        // Start a job in the transition scope.
+        val jobCompletable = CompletableDeferred<Unit>()
+        transition.coroutineScope.launch { jobCompletable.await() }
+
+        // Finish the transition (i.e. make its #run() method return). The transition should not be
+        // considered as finished yet given that there is a job still running in its scope.
+        transition.finish()
+        runCurrent()
+        assertThat(state.transitionState).isSceneTransition()
+
+        // Finish the job in the scope. Now the transition should be considered as finished.
+        jobCompletable.complete(Unit)
+        runCurrent()
+        assertThat(state.transitionState).isIdle()
+    }
+
+    @Test
+    fun transitionScopeIsCancelledWhenTransitionIsForceFinished() = runTest {
+        val state = MutableSceneTransitionLayoutState(SceneA)
+
+        // Start a transition.
+        val transition = transition(from = SceneA, to = SceneB)
+        state.startTransitionImmediately(backgroundScope, transition)
+        assertThat(state.transitionState).isSceneTransition()
+
+        // Start a job in the transition scope that never finishes.
+        val job = transition.coroutineScope.launch { awaitCancellation() }
+
+        // Force snap state to SceneB to force finish all current transitions.
+        state.snapToScene(SceneB)
+        assertThat(state.transitionState).isIdle()
+        assertThat(job.isCancelled).isTrue()
+    }
 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
index d317114..1f9ba9e 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
@@ -22,17 +22,22 @@
 import androidx.compose.animation.core.spring
 import androidx.compose.animation.core.tween
 import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.ui.unit.IntSize
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.compose.animation.scene.TestScenes.SceneA
 import com.android.compose.animation.scene.TestScenes.SceneB
 import com.android.compose.animation.scene.TestScenes.SceneC
 import com.android.compose.animation.scene.content.state.TransitionState
+import com.android.compose.animation.scene.transformation.CustomSizeTransformation
 import com.android.compose.animation.scene.transformation.OverscrollTranslate
+import com.android.compose.animation.scene.transformation.PropertyTransformationScope
 import com.android.compose.animation.scene.transformation.TransformationRange
 import com.android.compose.animation.scene.transformation.TransformationWithRange
 import com.android.compose.test.transition
 import com.google.common.truth.Correspondence
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.runTest
 import org.junit.Assert.assertThrows
 import org.junit.Test
@@ -343,6 +348,33 @@
         assertThat(transitionPassedToBuilder).isSameInstanceAs(transition)
     }
 
+    @Test
+    fun customTransitionsAreNotSupportedInRanges() = runTest {
+        val transitions = transitions {
+            from(SceneA, to = SceneB) {
+                fractionRange {
+                    transformation(
+                        object : CustomSizeTransformation {
+                            override val matcher: ElementMatcher = TestElements.Foo
+
+                            override fun PropertyTransformationScope.transform(
+                                content: ContentKey,
+                                element: ElementKey,
+                                transition: TransitionState.Transition,
+                                transitionScope: CoroutineScope,
+                            ): IntSize = IntSize.Zero
+                        }
+                    )
+                }
+            }
+        }
+
+        val state = MutableSceneTransitionLayoutState(SceneA, transitions)
+        assertThrows(IllegalStateException::class.java) {
+            runBlocking { state.startTransition(transition(from = SceneA, to = SceneB)) }
+        }
+    }
+
     companion object {
         private val TRANSFORMATION_RANGE =
             Correspondence.transforming<TransformationWithRange<*>, TransformationRange?>(
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/CustomTransformationTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/CustomTransformationTest.kt
new file mode 100644
index 0000000..487b099
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/CustomTransformationTest.kt
@@ -0,0 +1,120 @@
+/*
+ * 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.compose.animation.scene.transformation
+
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.test.assertPositionInRootIsEqualTo
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.ContentKey
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.ElementMatcher
+import com.android.compose.animation.scene.TestElements
+import com.android.compose.animation.scene.content.state.TransitionState
+import com.android.compose.animation.scene.testTransition
+import com.android.compose.test.assertSizeIsEqualTo
+import kotlinx.coroutines.CoroutineScope
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class CustomTransformationTest {
+    @get:Rule val rule = createComposeRule()
+
+    @Test
+    fun customSize() {
+        /** A size transformation that adds [add] to the size of the transformed element(s). */
+        class AddSizeTransformation(override val matcher: ElementMatcher, private val add: Dp) :
+            CustomSizeTransformation {
+            override fun PropertyTransformationScope.transform(
+                content: ContentKey,
+                element: ElementKey,
+                transition: TransitionState.Transition,
+                transitionScope: CoroutineScope,
+            ): IntSize {
+                val idleSize = checkNotNull(element.targetSize(content))
+                val progress = 1f - transition.progressTo(content)
+                val addPx = (add * progress).roundToPx()
+                return IntSize(width = idleSize.width + addPx, height = idleSize.height + addPx)
+            }
+        }
+
+        rule.testTransition(
+            fromSceneContent = { Box(Modifier.element(TestElements.Foo).size(40.dp, 20.dp)) },
+            toSceneContent = {},
+            transition = {
+                spec = tween(16 * 4, easing = LinearEasing)
+
+                // Add 80dp to the width and height of Foo.
+                transformation(AddSizeTransformation(TestElements.Foo, 80.dp))
+            },
+        ) {
+            before { onElement(TestElements.Foo).assertSizeIsEqualTo(40.dp, 20.dp) }
+            at(0) { onElement(TestElements.Foo).assertSizeIsEqualTo(40.dp, 20.dp) }
+            at(16) { onElement(TestElements.Foo).assertSizeIsEqualTo(60.dp, 40.dp) }
+            at(32) { onElement(TestElements.Foo).assertSizeIsEqualTo(80.dp, 60.dp) }
+            at(48) { onElement(TestElements.Foo).assertSizeIsEqualTo(100.dp, 80.dp) }
+            after { onElement(TestElements.Foo).assertDoesNotExist() }
+        }
+    }
+
+    @Test
+    fun customOffset() {
+        /** An offset transformation that adds [add] to the offset of the transformed element(s). */
+        class AddOffsetTransformation(override val matcher: ElementMatcher, private val add: Dp) :
+            CustomOffsetTransformation {
+            override fun PropertyTransformationScope.transform(
+                content: ContentKey,
+                element: ElementKey,
+                transition: TransitionState.Transition,
+                transitionScope: CoroutineScope,
+            ): Offset {
+                val idleOffset = checkNotNull(element.targetOffset(content))
+                val progress = 1f - transition.progressTo(content)
+                val addPx = (add * progress).toPx()
+                return Offset(x = idleOffset.x + addPx, y = idleOffset.y + addPx)
+            }
+        }
+
+        rule.testTransition(
+            fromSceneContent = { Box(Modifier.element(TestElements.Foo)) },
+            toSceneContent = {},
+            transition = {
+                spec = tween(16 * 4, easing = LinearEasing)
+
+                // Add 80dp to the offset of Foo.
+                transformation(AddOffsetTransformation(TestElements.Foo, 80.dp))
+            },
+        ) {
+            before { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(0.dp, 0.dp) }
+            at(0) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(0.dp, 0.dp) }
+            at(16) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(20.dp, 20.dp) }
+            at(32) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(40.dp, 40.dp) }
+            at(48) { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(60.dp, 60.dp) }
+            after { onElement(TestElements.Foo).assertDoesNotExist() }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
index 91079b8..28ea2d2 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
@@ -53,7 +53,8 @@
         object : FlingBehavior {
             override suspend fun ScrollScope.performFling(initialVelocity: Float): Float {
                 scrollBy(initialVelocity)
-                return initialVelocity / 2f
+                // returns the remaining velocity: 1/3 remained + 2/3 consumed
+                return initialVelocity / 3f
             }
         }
 
@@ -207,11 +208,13 @@
 
         val consumed = scrollConnection.onPreFling(available = Velocity(2f, 2f))
 
-        assertThat(lastStop).isEqualTo(2f)
+        val initialVelocity = 2f
+        assertThat(lastStop).isEqualTo(initialVelocity)
         // flingToScroll should try to scroll the content, customFlingBehavior uses the velocity.
         assertThat(lastScroll).isEqualTo(2f)
-        // customFlingBehavior returns half of the vertical velocity.
-        assertThat(consumed).isEqualTo(Velocity(0f, 1f))
+        val remainingVelocity = initialVelocity / 3f
+        // customFlingBehavior returns 2/3 of the vertical velocity.
+        assertThat(consumed).isEqualTo(Velocity(0f, initialVelocity - remainingVelocity))
     }
 
     @Test
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/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
index fcb433b..9d471f4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
@@ -45,9 +45,9 @@
 import android.platform.test.annotations.EnableFlags;
 import android.provider.Settings;
 import android.testing.TestableLooper;
+import android.util.Pair;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.LinearLayout;
 import android.widget.Space;
 import android.widget.Spinner;
 
@@ -166,6 +166,7 @@
         when(mCachedDevice.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(true);
         when(mCachedDevice.isConnectedHearingAidDevice()).thenReturn(true);
         when(mCachedDevice.isConnectedHapClientDevice()).thenReturn(true);
+        when(mCachedDevice.getDrawableWithDescription()).thenReturn(new Pair<>(mDrawable, ""));
         when(mHearingDeviceItem.getCachedBluetoothDevice()).thenReturn(mCachedDevice);
 
         mContext.setMockPackageManager(mPackageManager);
@@ -217,6 +218,18 @@
 
     @Test
     @EnableFlags(Flags.FLAG_HEARING_DEVICES_DIALOG_RELATED_TOOLS)
+    public void showDialog_noLiveCaption_noRelatedToolsInConfig_relatedToolLayoutGone() {
+        mContext.getOrCreateTestableResources().addOverride(
+                R.array.config_quickSettingsHearingDevicesRelatedToolName, new String[]{});
+
+        setUpPairNewDeviceDialog();
+        mDialog.show();
+
+        assertToolsUi(0);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_HEARING_DEVICES_DIALOG_RELATED_TOOLS)
     public void showDialog_hasLiveCaption_noRelatedToolsInConfig_showOneRelatedTool() {
         when(mPackageManager.queryIntentActivities(
                 eq(LIVE_CAPTION_INTENT), anyInt())).thenReturn(
@@ -227,8 +240,7 @@
         setUpPairNewDeviceDialog();
         mDialog.show();
 
-        LinearLayout relatedToolsView = (LinearLayout) getRelatedToolsView(mDialog);
-        assertThat(countChildWithoutSpace(relatedToolsView)).isEqualTo(1);
+        assertToolsUi(1);
     }
 
     @Test
@@ -251,8 +263,7 @@
         setUpPairNewDeviceDialog();
         mDialog.show();
 
-        LinearLayout relatedToolsView = (LinearLayout) getRelatedToolsView(mDialog);
-        assertThat(countChildWithoutSpace(relatedToolsView)).isEqualTo(2);
+        assertToolsUi(2);
     }
 
     @Test
@@ -263,8 +274,8 @@
         setUpDeviceListDialog();
         mDialog.show();
 
-        Spinner spinner = (Spinner) getPresetSpinner(mDialog);
-        assertThat(spinner.getVisibility()).isEqualTo(View.GONE);
+        ViewGroup presetLayout = getPresetLayout(mDialog);
+        assertThat(presetLayout.getVisibility()).isEqualTo(View.GONE);
     }
 
     @Test
@@ -276,8 +287,9 @@
         setUpDeviceListDialog();
         mDialog.show();
 
-        Spinner spinner = (Spinner) getPresetSpinner(mDialog);
-        assertThat(spinner.getVisibility()).isEqualTo(View.VISIBLE);
+        ViewGroup presetLayout = getPresetLayout(mDialog);
+        assertThat(presetLayout.getVisibility()).isEqualTo(View.VISIBLE);
+        Spinner spinner = getPresetSpinner(mDialog);
         assertThat(spinner.getSelectedItemPosition()).isEqualTo(0);
     }
 
@@ -292,8 +304,9 @@
         mDialogDelegate.onActiveDeviceChanged(mCachedDevice, BluetoothProfile.LE_AUDIO);
         mTestableLooper.processAllMessages();
 
-        Spinner spinner = (Spinner) getPresetSpinner(mDialog);
-        assertThat(spinner.getVisibility()).isEqualTo(View.VISIBLE);
+        ViewGroup presetLayout = getPresetLayout(mDialog);
+        assertThat(presetLayout.getVisibility()).isEqualTo(View.VISIBLE);
+        Spinner spinner = getPresetSpinner(mDialog);
         assertThat(spinner.getSelectedItemPosition()).isEqualTo(0);
     }
 
@@ -359,14 +372,23 @@
         return dialog.requireViewById(R.id.pair_new_device_button);
     }
 
-    private View getRelatedToolsView(SystemUIDialog dialog) {
-        return dialog.requireViewById(R.id.related_tools_container);
+    private ViewGroup getToolsContainer(SystemUIDialog dialog) {
+        return dialog.requireViewById(R.id.tools_container);
     }
 
-    private View getPresetSpinner(SystemUIDialog dialog) {
+    private ViewGroup getToolsLayout(SystemUIDialog dialog) {
+        return dialog.requireViewById(R.id.tools_layout);
+    }
+
+    private Spinner getPresetSpinner(SystemUIDialog dialog) {
         return dialog.requireViewById(R.id.preset_spinner);
     }
 
+    private ViewGroup getPresetLayout(SystemUIDialog dialog) {
+        return dialog.requireViewById(R.id.preset_layout);
+    }
+
+
     private int countChildWithoutSpace(ViewGroup viewGroup) {
         int spaceCount = 0;
         for (int i = 0; i < viewGroup.getChildCount(); i++) {
@@ -377,6 +399,15 @@
         return viewGroup.getChildCount() - spaceCount;
     }
 
+    private void assertToolsUi(int childCount) {
+        ViewGroup toolsContainer = getToolsContainer(mDialog);
+        assertThat(countChildWithoutSpace(toolsContainer)).isEqualTo(childCount);
+
+        int targetVisibility = childCount == 0 ? View.GONE : View.VISIBLE;
+        ViewGroup toolsLayout = getToolsLayout(mDialog);
+        assertThat(toolsLayout.getVisibility()).isEqualTo(targetVisibility);
+    }
+
     @After
     public void reset() {
         if (mDialogDelegate != null) {
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/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorTest.kt
index f06b105..cee17c3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorTest.kt
@@ -18,7 +18,9 @@
 
 import android.bluetooth.BluetoothAdapter
 import android.bluetooth.BluetoothDevice
+import android.graphics.drawable.Drawable
 import android.testing.TestableLooper
+import android.util.Pair
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
@@ -64,6 +66,7 @@
     @Mock private lateinit var bluetoothDevice1: BluetoothDevice
     @Mock private lateinit var cachedDevice2: CachedBluetoothDevice
     @Mock private lateinit var bluetoothDevice2: BluetoothDevice
+    @Mock private lateinit var drawable: Drawable
     @Captor
     private lateinit var argumentCaptor: ArgumentCaptor<BluetoothAdapter.OnMetadataChangedListener>
     private lateinit var interactor: BluetoothDeviceMetadataInteractor
@@ -77,12 +80,14 @@
             whenever(cachedDevice1.name).thenReturn(DEVICE_NAME)
             whenever(cachedDevice1.address).thenReturn(DEVICE_ADDRESS)
             whenever(cachedDevice1.connectionSummary).thenReturn(CONNECTION_SUMMARY)
+            whenever(cachedDevice1.drawableWithDescription).thenReturn(Pair.create(drawable, ""))
             whenever(bluetoothDevice1.address).thenReturn(DEVICE_ADDRESS)
 
             whenever(cachedDevice2.device).thenReturn(bluetoothDevice2)
             whenever(cachedDevice2.name).thenReturn(DEVICE_NAME)
             whenever(cachedDevice2.address).thenReturn(DEVICE_ADDRESS)
             whenever(cachedDevice2.connectionSummary).thenReturn(CONNECTION_SUMMARY)
+            whenever(cachedDevice2.drawableWithDescription).thenReturn(Pair.create(drawable, ""))
             whenever(bluetoothDevice2.address).thenReturn(DEVICE_ADDRESS)
 
             interactor = bluetoothDeviceMetadataInteractor
@@ -175,14 +180,14 @@
                     .addOnMetadataChangedListener(
                         eq(bluetoothDevice1),
                         any(),
-                        argumentCaptor.capture()
+                        argumentCaptor.capture(),
                     )
 
                 val listener = argumentCaptor.value
                 listener.onMetadataChanged(
                     bluetoothDevice1,
                     BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY,
-                    ByteArray(0)
+                    ByteArray(0),
                 )
                 assertThat(update).isEqualTo(Unit)
             }
@@ -203,14 +208,14 @@
                     .addOnMetadataChangedListener(
                         eq(bluetoothDevice1),
                         any(),
-                        argumentCaptor.capture()
+                        argumentCaptor.capture(),
                     )
 
                 val listener = argumentCaptor.value
                 listener.onMetadataChanged(
                     bluetoothDevice1,
                     BluetoothDevice.METADATA_MODEL_NAME,
-                    ByteArray(0)
+                    ByteArray(0),
                 )
 
                 assertThat(update).isNull()
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/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/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/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/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
index 3d5498b..7a3089f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -527,6 +527,29 @@
             assertEquals(0, steps.size)
         }
 
+    @Test
+    fun testForceFinishCurrentTransition_noTransitionRunning_unlocksMutex() =
+        testScope.runTest {
+            val steps by collectValues(underTest.transitions.dropWhile { step -> step.from == OFF })
+            underTest.forceFinishCurrentTransition()
+
+            assertThat(steps.isEmpty())
+
+            underTest.forceFinishCurrentTransition()
+            runCurrent()
+
+            assertThat(steps.isEmpty())
+            runner.startTransition(
+                this,
+                TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, getAnimator()),
+                maxFrames = 100,
+            )
+
+            advanceTimeBy(5000L)
+
+            assertThat(steps.isNotEmpty())
+        }
+
     private fun listWithStep(
         step: BigDecimal,
         start: BigDecimal = BigDecimal.ZERO,
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/multivalentTests/src/com/android/systemui/qs/QSFooterViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
index 93dede5..f1f6c61 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
@@ -38,7 +38,7 @@
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.res.R;
 import com.android.systemui.retail.data.repository.FakeRetailModeRepository;
-import com.android.systemui.retail.domain.interactor.RetailModeInteractorImpl;
+import com.android.systemui.retail.domain.interactor.impl.RetailModeInteractorImpl;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.utils.leaks.LeakCheckedTest;
 
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/multivalentTests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt
index ba7a65d..47bfda4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt
@@ -21,6 +21,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.retail.data.repository.impl.RetailModeSettingsRepository
 import com.android.systemui.util.settings.FakeGlobalSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt
index b536520..b47dcb5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt
@@ -20,6 +20,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.retail.data.repository.FakeRetailModeRepository
+import com.android.systemui.retail.domain.interactor.impl.RetailModeInteractorImpl
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
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/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt
index ca75ca6..a70d24e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt
@@ -28,43 +28,41 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderEntryListener
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderGroupListener
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderListListener
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.withArgCaptor
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.inOrder
-import org.mockito.Mockito.never
-import org.mockito.Mockito.spy
-import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.inOrder
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyNoMoreInteractions
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class RenderStageManagerTest : SysuiTestCase() {
 
-    @Mock private lateinit var shadeListBuilder: ShadeListBuilder
-    @Mock private lateinit var onAfterRenderListListener: OnAfterRenderListListener
-    @Mock private lateinit var onAfterRenderGroupListener: OnAfterRenderGroupListener
-    @Mock private lateinit var onAfterRenderEntryListener: OnAfterRenderEntryListener
+    private val shadeListBuilder: ShadeListBuilder = mock()
+    private val onAfterRenderListListener: OnAfterRenderListListener = mock()
+    private val onAfterRenderGroupListener: OnAfterRenderGroupListener = mock()
+    private val onAfterRenderEntryListener: OnAfterRenderEntryListener = mock()
 
-    private lateinit var onRenderListListener: ShadeListBuilder.OnRenderListListener
-    private lateinit var renderStageManager: RenderStageManager
     private val spyViewRenderer = spy(FakeNotifViewRenderer())
+    private lateinit var onRenderListListener: ShadeListBuilder.OnRenderListListener
+
+    private lateinit var renderStageManager: RenderStageManager
 
     @Before
     fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
         renderStageManager = RenderStageManager()
         renderStageManager.attach(shadeListBuilder)
-        onRenderListListener = withArgCaptor {
-            verify(shadeListBuilder).setOnRenderListListener(capture())
-        }
+
+        val captor = argumentCaptor<ShadeListBuilder.OnRenderListListener>()
+        verify(shadeListBuilder).setOnRenderListListener(captor.capture())
+        onRenderListListener = captor.lastValue
     }
 
     private fun setUpRenderer() {
@@ -89,7 +87,7 @@
         verifyNoMoreInteractions(
             onAfterRenderListListener,
             onAfterRenderGroupListener,
-            onAfterRenderEntryListener
+            onAfterRenderEntryListener,
         )
     }
 
@@ -171,7 +169,7 @@
         verifyNoMoreInteractions(
             onAfterRenderListListener,
             onAfterRenderGroupListener,
-            onAfterRenderEntryListener
+            onAfterRenderEntryListener,
         )
     }
 
@@ -191,30 +189,27 @@
         verifyNoMoreInteractions(
             onAfterRenderListListener,
             onAfterRenderGroupListener,
-            onAfterRenderEntryListener
+            onAfterRenderEntryListener,
         )
     }
 
-    private fun listWith2Groups8Entries() = listOf(
-        group(
-            notif(1),
-            notif(2),
-            notif(3)
-        ),
-        notif(4),
-        group(
-            notif(5),
-            notif(6),
-            notif(7)
-        ),
-        notif(8)
-    )
+    private fun listWith2Groups8Entries() =
+        listOf(
+            group(notif(1), notif(2), notif(3)),
+            notif(4),
+            group(notif(5), notif(6), notif(7)),
+            notif(8),
+        )
 
     private class FakeNotifViewRenderer : NotifViewRenderer {
         override fun onRenderList(notifList: List<ListEntry>) {}
+
         override fun getStackController(): NotifStackController = mock()
+
         override fun getGroupController(group: GroupEntry): NotifGroupController = mock()
+
         override fun getRowController(entry: NotificationEntry): NotifRowController = mock()
+
         override fun onDispatchComplete() {}
     }
 
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/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index b2a485c..b877456 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -32,23 +32,19 @@
 import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.policy.AvalancheController
-import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Expect
 import com.google.common.truth.Truth.assertThat
-import junit.framework.Assert.assertEquals
-import junit.framework.Assert.assertFalse
-import junit.framework.Assert.assertTrue
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import org.junit.Assume
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mockito.any
-import org.mockito.Mockito.eq
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
 import platform.test.runner.parameterized.ParameterizedAndroidJunit4
 import platform.test.runner.parameterized.Parameters
 
@@ -846,7 +842,7 @@
         val viewStart = 0f
         val shelfStart = 1f
 
-        val expandableView = mock(ExpandableView::class.java)
+        val expandableView = mock<ExpandableView>()
         whenever(expandableView.isExpandAnimationRunning).thenReturn(false)
         whenever(expandableView.hasExpandingChild()).thenReturn(false)
 
@@ -854,7 +850,7 @@
         expandableViewState.yTranslation = viewStart
 
         stackScrollAlgorithm.updateViewWithShelf(expandableView, expandableViewState, shelfStart)
-        assertFalse(expandableViewState.hidden)
+        assertThat(expandableViewState.hidden).isFalse()
     }
 
     @Test
@@ -862,7 +858,7 @@
         val shelfStart = 0f
         val viewStart = 1f
 
-        val expandableView = mock(ExpandableView::class.java)
+        val expandableView = mock<ExpandableView>()
         whenever(expandableView.isExpandAnimationRunning).thenReturn(false)
         whenever(expandableView.hasExpandingChild()).thenReturn(false)
 
@@ -870,7 +866,7 @@
         expandableViewState.yTranslation = viewStart
 
         stackScrollAlgorithm.updateViewWithShelf(expandableView, expandableViewState, shelfStart)
-        assertTrue(expandableViewState.hidden)
+        assertThat(expandableViewState.hidden).isTrue()
     }
 
     @Test
@@ -878,7 +874,7 @@
         val shelfStart = 0f
         val viewStart = 1f
 
-        val expandableView = mock(ExpandableView::class.java)
+        val expandableView = mock<ExpandableView>()
         whenever(expandableView.isExpandAnimationRunning).thenReturn(true)
         whenever(expandableView.hasExpandingChild()).thenReturn(true)
 
@@ -886,7 +882,7 @@
         expandableViewState.yTranslation = viewStart
 
         stackScrollAlgorithm.updateViewWithShelf(expandableView, expandableViewState, shelfStart)
-        assertFalse(expandableViewState.hidden)
+        assertThat(expandableViewState.hidden).isFalse()
     }
 
     @Test
@@ -898,12 +894,12 @@
             expandableViewState,
             /* isShadeExpanded= */ true,
             /* mustStayOnScreen= */ true,
-            /* isViewEndVisible= */ true,
+            /* topVisible = */ true,
             /* viewEnd= */ 0f,
-            /* maxHunY= */ 10f,
+            /* hunMax = */ 10f,
         )
 
-        assertTrue(expandableViewState.headsUpIsVisible)
+        assertThat(expandableViewState.headsUpIsVisible).isTrue()
     }
 
     @Test
@@ -915,12 +911,12 @@
             expandableViewState,
             /* isShadeExpanded= */ true,
             /* mustStayOnScreen= */ true,
-            /* isViewEndVisible= */ true,
+            /* topVisible = */ true,
             /* viewEnd= */ 10f,
-            /* maxHunY= */ 0f,
+            /* hunMax = */ 0f,
         )
 
-        assertFalse(expandableViewState.headsUpIsVisible)
+        assertThat(expandableViewState.headsUpIsVisible).isFalse()
     }
 
     @Test
@@ -932,12 +928,12 @@
             expandableViewState,
             /* isShadeExpanded= */ false,
             /* mustStayOnScreen= */ true,
-            /* isViewEndVisible= */ true,
+            /* topVisible = */ true,
             /* viewEnd= */ 10f,
-            /* maxHunY= */ 1f,
+            /* hunMax = */ 1f,
         )
 
-        assertTrue(expandableViewState.headsUpIsVisible)
+        assertThat(expandableViewState.headsUpIsVisible).isTrue()
     }
 
     @Test
@@ -949,12 +945,12 @@
             expandableViewState,
             /* isShadeExpanded= */ true,
             /* mustStayOnScreen= */ false,
-            /* isViewEndVisible= */ true,
+            /* topVisible = */ true,
             /* viewEnd= */ 10f,
-            /* maxHunY= */ 1f,
+            /* hunMax = */ 1f,
         )
 
-        assertTrue(expandableViewState.headsUpIsVisible)
+        assertThat(expandableViewState.headsUpIsVisible).isTrue()
     }
 
     @Test
@@ -966,12 +962,12 @@
             expandableViewState,
             /* isShadeExpanded= */ true,
             /* mustStayOnScreen= */ true,
-            /* isViewEndVisible= */ false,
+            /* topVisible = */ false,
             /* viewEnd= */ 10f,
-            /* maxHunY= */ 1f,
+            /* hunMax = */ 1f,
         )
 
-        assertTrue(expandableViewState.headsUpIsVisible)
+        assertThat(expandableViewState.headsUpIsVisible).isTrue()
     }
 
     @Test
@@ -986,7 +982,7 @@
         )
 
         // qqs (10 + 0) < viewY (50)
-        assertEquals(50f, expandableViewState.yTranslation)
+        assertThat(expandableViewState.yTranslation).isEqualTo(50f)
     }
 
     @Test
@@ -1001,7 +997,7 @@
         )
 
         // qqs (10 + 0) > viewY (-10)
-        assertEquals(10f, expandableViewState.yTranslation)
+        assertThat(expandableViewState.yTranslation).isEqualTo(10f)
     }
 
     @Test
@@ -1019,7 +1015,7 @@
         // newTranslation = max(10, -100) = 10
         // distToRealY = 10 - (-100f) = 110
         // height = max(20 - 110, 10f)
-        assertEquals(10, expandableViewState.height)
+        assertThat(expandableViewState.height).isEqualTo(10)
     }
 
     @Test
@@ -1037,7 +1033,7 @@
         // newTranslation = max(10, 5) = 10
         // distToRealY = 10 - 5 = 5
         // height = max(20 - 5, 10) = 15
-        assertEquals(15, expandableViewState.height)
+        assertThat(expandableViewState.height).isEqualTo(15)
     }
 
     @Test
@@ -1047,9 +1043,9 @@
                 /* hostViewHeight= */ 100f,
                 /* stackY= */ 110f,
                 /* viewMaxHeight= */ 20f,
-                /* originalCornerRoundness= */ 0f,
+                /* originalCornerRadius = */ 0f,
             )
-        assertEquals(1f, currentRoundness)
+        assertThat(currentRoundness).isEqualTo(1f)
     }
 
     @Test
@@ -1059,9 +1055,9 @@
                 /* hostViewHeight= */ 100f,
                 /* stackY= */ 90f,
                 /* viewMaxHeight= */ 20f,
-                /* originalCornerRoundness= */ 0f,
+                /* originalCornerRadius = */ 0f,
             )
-        assertEquals(0.5f, currentRoundness)
+        assertThat(currentRoundness).isEqualTo(0.5f)
     }
 
     @Test
@@ -1071,9 +1067,9 @@
                 /* hostViewHeight= */ 100f,
                 /* stackY= */ 0f,
                 /* viewMaxHeight= */ 20f,
-                /* originalCornerRoundness= */ 0f,
+                /* originalCornerRadius = */ 0f,
             )
-        assertEquals(0f, currentRoundness)
+        assertThat(currentRoundness).isZero()
     }
 
     @Test
@@ -1083,9 +1079,9 @@
                 /* hostViewHeight= */ 100f,
                 /* stackY= */ 0f,
                 /* viewMaxHeight= */ 20f,
-                /* originalCornerRoundness= */ 1f,
+                /* originalCornerRadius = */ 1f,
             )
-        assertEquals(1f, currentRoundness)
+        assertThat(currentRoundness).isEqualTo(1f)
     }
 
     @Test
@@ -1105,13 +1101,14 @@
         stackScrollAlgorithm.updateChildZValue(
             /* i= */ 0,
             /* childrenOnTop= */ 0.0f,
-            /* StackScrollAlgorithmState= */ algorithmState,
+            /* algorithmState = */ algorithmState,
             /* ambientState= */ ambientState,
-            /* shouldElevateHun= */ true,
+            /* isTopHun = */ true,
         )
 
         // Then: full shadow would be applied
-        assertEquals(px(R.dimen.heads_up_pinned_elevation), childHunView.viewState.zTranslation)
+        assertThat(childHunView.viewState.zTranslation)
+            .isEqualTo(px(R.dimen.heads_up_pinned_elevation))
     }
 
     @Test
@@ -1133,9 +1130,9 @@
         stackScrollAlgorithm.updateChildZValue(
             /* i= */ 0,
             /* childrenOnTop= */ 0.0f,
-            /* StackScrollAlgorithmState= */ algorithmState,
+            /* algorithmState = */ algorithmState,
             /* ambientState= */ ambientState,
-            /* shouldElevateHun= */ true,
+            /* isTopHun = */ true,
         )
 
         // Then: HUN should have shadow, but not as full size
@@ -1166,13 +1163,13 @@
         stackScrollAlgorithm.updateChildZValue(
             /* i= */ 0,
             /* childrenOnTop= */ 0.0f,
-            /* StackScrollAlgorithmState= */ algorithmState,
+            /* algorithmState = */ algorithmState,
             /* ambientState= */ ambientState,
-            /* shouldElevateHun= */ true,
+            /* isTopHun = */ true,
         )
 
         // Then: HUN should not have shadow
-        assertEquals(0f, childHunView.viewState.zTranslation)
+        assertThat(childHunView.viewState.zTranslation).isZero()
     }
 
     @Test
@@ -1195,13 +1192,14 @@
         stackScrollAlgorithm.updateChildZValue(
             /* i= */ 0,
             /* childrenOnTop= */ 0.0f,
-            /* StackScrollAlgorithmState= */ algorithmState,
+            /* algorithmState = */ algorithmState,
             /* ambientState= */ ambientState,
-            /* shouldElevateHun= */ true,
+            /* isTopHun = */ true,
         )
 
         // Then: HUN should have full shadow
-        assertEquals(px(R.dimen.heads_up_pinned_elevation), childHunView.viewState.zTranslation)
+        assertThat(childHunView.viewState.zTranslation)
+            .isEqualTo(px(R.dimen.heads_up_pinned_elevation))
     }
 
     @Test
@@ -1225,9 +1223,9 @@
         stackScrollAlgorithm.updateChildZValue(
             /* i= */ 0,
             /* childrenOnTop= */ 0.0f,
-            /* StackScrollAlgorithmState= */ algorithmState,
+            /* algorithmState = */ algorithmState,
             /* ambientState= */ ambientState,
-            /* shouldElevateHun= */ true,
+            /* isTopHun = */ true,
         )
 
         // Then: HUN should have shadow, but not as full size
@@ -1251,7 +1249,7 @@
         stackScrollAlgorithm.updatePulsingStates(algorithmState, ambientState)
 
         // Then: ambientState.pulsingRow should still be pulsingNotificationView
-        assertTrue(ambientState.isPulsingRow(pulsingNotificationView))
+        assertThat(ambientState.isPulsingRow(pulsingNotificationView)).isTrue()
     }
 
     @Test
@@ -1268,7 +1266,7 @@
         stackScrollAlgorithm.updatePulsingStates(algorithmState, ambientState)
 
         // Then: ambientState.pulsingRow should record the pulsingNotificationView
-        assertTrue(ambientState.isPulsingRow(pulsingNotificationView))
+        assertThat(ambientState.isPulsingRow(pulsingNotificationView)).isTrue()
     }
 
     @Test
@@ -1287,7 +1285,7 @@
         stackScrollAlgorithm.updatePulsingStates(algorithmState, ambientState)
 
         // Then: ambientState.pulsingRow should be null
-        assertTrue(ambientState.isPulsingRow(null))
+        assertThat(ambientState.isPulsingRow(null)).isTrue()
     }
 
     @Test
@@ -1310,10 +1308,8 @@
         stackScrollAlgorithm.resetViewStates(ambientState, 0)
 
         // Then: pulsingNotificationView should show at full height
-        assertEquals(
-            stackScrollAlgorithm.getMaxAllowedChildHeight(pulsingNotificationView),
-            pulsingNotificationView.viewState.height,
-        )
+        assertThat(pulsingNotificationView.viewState.height)
+            .isEqualTo(stackScrollAlgorithm.getMaxAllowedChildHeight(pulsingNotificationView))
 
         // After: reset dozeAmount and expansionFraction
         ambientState.dozeAmount = 0f
@@ -1418,7 +1414,7 @@
                 yTranslation = ambientState.maxHeadsUpTranslation - height // move it to the max
             }
 
-        assertTrue(stackScrollAlgorithm.shouldHunAppearFromBottom(ambientState, viewState))
+        assertThat(stackScrollAlgorithm.shouldHunAppearFromBottom(ambientState, viewState)).isTrue()
     }
 
     @Test
@@ -1431,7 +1427,8 @@
                     ambientState.maxHeadsUpTranslation - height - 1 // move it below the max
             }
 
-        assertFalse(stackScrollAlgorithm.shouldHunAppearFromBottom(ambientState, viewState))
+        assertThat(stackScrollAlgorithm.shouldHunAppearFromBottom(ambientState, viewState))
+            .isFalse()
     }
 
     // endregion
@@ -1579,13 +1576,13 @@
 }
 
 private fun mockExpandableNotificationRow(): ExpandableNotificationRow {
-    return mock(ExpandableNotificationRow::class.java).apply {
+    return mock<ExpandableNotificationRow>().apply {
         whenever(viewState).thenReturn(ExpandableViewState())
     }
 }
 
 private fun mockFooterView(height: Int): FooterView {
-    return mock(FooterView::class.java).apply {
+    return mock<FooterView>().apply {
         whenever(viewState).thenReturn(FooterViewState())
         whenever(intrinsicHeight).thenReturn(height)
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
index 778e822..7a51b2d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
@@ -1088,7 +1088,7 @@
 
     @Test
     @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
-    fun onMaxBoundsChanged_beforeStart_flagDisabled_listenerNotNotified() {
+    fun onMaxBoundsChanged_beforeStart_flagDisabled_listenerNotified() {
         // Start out with an existing configuration with bounds
         configuration.windowConfiguration.setMaxBounds(0, 0, 100, 100)
         configurationController.onConfigurationChanged(configuration)
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/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
index 39836e2..0e32c95 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
@@ -18,9 +18,12 @@
 
 package com.android.systemui.statusbar.policy.ui.dialog.viewmodel
 
+import android.app.AutomaticZenRule
 import android.content.Intent
 import android.content.applicationContext
 import android.provider.Settings
+import android.service.notification.SystemZenRules
+import android.service.notification.ZenModeConfig
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.settingslib.notification.modes.TestModeBuilder
@@ -35,6 +38,7 @@
 import com.android.systemui.statusbar.policy.ui.dialog.mockModesDialogEventLogger
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
+import java.util.Calendar
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.test.runCurrent
@@ -60,6 +64,8 @@
 
     private lateinit var underTest: ModesDialogViewModel
 
+    private lateinit var timeScheduleMode: ZenMode
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -71,6 +77,21 @@
                 kosmos.mockModesDialogDelegate,
                 kosmos.mockModesDialogEventLogger,
             )
+
+        val scheduleInfo = ZenModeConfig.ScheduleInfo()
+        scheduleInfo.days = intArrayOf(Calendar.MONDAY, Calendar.TUESDAY, Calendar.WEDNESDAY)
+        scheduleInfo.startHour = 11
+        scheduleInfo.endHour = 15
+        timeScheduleMode =
+            TestModeBuilder()
+                .setPackage(SystemZenRules.PACKAGE_ANDROID)
+                .setType(AutomaticZenRule.TYPE_SCHEDULE_TIME)
+                .setManualInvocationAllowed(true)
+                .setConditionId(ZenModeConfig.toScheduleConditionId(scheduleInfo))
+                .setTriggerDescription(
+                    SystemZenRules.getTriggerDescriptionForScheduleTime(mContext, scheduleInfo)
+                )
+                .build()
     }
 
     @Test
@@ -325,17 +346,19 @@
                         .setTriggerDescription(null)
                         .setEnabled(false, /* byUser= */ false)
                         .build(),
+                    timeScheduleMode,
                 )
             )
             runCurrent()
 
-            assertThat(tiles!!).hasSize(6)
+            assertThat(tiles!!).hasSize(7)
             assertThat(tiles!![0].subtext).isEqualTo("When the going gets tough")
             assertThat(tiles!![1].subtext).isEqualTo("On • When in Rome")
             assertThat(tiles!![2].subtext).isEqualTo("Not set")
             assertThat(tiles!![3].subtext).isEqualTo("Off")
             assertThat(tiles!![4].subtext).isEqualTo("On")
             assertThat(tiles!![5].subtext).isEqualTo("Not set")
+            assertThat(tiles!![6].subtext).isEqualTo("Mon - Wed, 11:00 AM - 3:00 PM")
         }
 
     @Test
@@ -381,11 +404,12 @@
                         .setTriggerDescription(null)
                         .setEnabled(false, /* byUser= */ false)
                         .build(),
+                    timeScheduleMode,
                 )
             )
             runCurrent()
 
-            assertThat(tiles!!).hasSize(6)
+            assertThat(tiles!!).hasSize(7)
             with(tiles?.elementAt(0)!!) {
                 assertThat(this.stateDescription).isEqualTo("Off")
                 assertThat(this.subtextDescription).isEqualTo("When the going gets tough")
@@ -410,6 +434,11 @@
                 assertThat(this.stateDescription).isEqualTo("Off")
                 assertThat(this.subtextDescription).isEqualTo("Not set")
             }
+            with(tiles?.elementAt(6)!!) {
+                assertThat(this.stateDescription).isEqualTo("Off")
+                assertThat(this.subtextDescription)
+                    .isEqualTo("Monday to Wednesday, 11:00 AM - 3:00 PM")
+            }
 
             // All tiles have the same long click info
             tiles!!.forEach { assertThat(it.onLongClickLabel).isEqualTo("Open settings") }
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/pods/Android.bp b/packages/SystemUI/pods/Android.bp
new file mode 100644
index 0000000..e45f317
--- /dev/null
+++ b/packages/SystemUI/pods/Android.bp
@@ -0,0 +1,22 @@
+//
+// 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 {
+    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+    // The package default_visibility specified here is inherited to subpackages that do not
+    // specify default_visibility:
+    default_visibility: ["//visibility:private"],
+}
diff --git a/packages/SystemUI/pods/com/android/systemui/dagger/Android.bp b/packages/SystemUI/pods/com/android/systemui/dagger/Android.bp
new file mode 100644
index 0000000..df90be8
--- /dev/null
+++ b/packages/SystemUI/pods/com/android/systemui/dagger/Android.bp
@@ -0,0 +1,34 @@
+//
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+soong_namespace {
+}
+
+package {
+    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+java_library {
+    name: "api",
+    srcs: [
+        "**/*.java",
+        "**/*.kt",
+    ],
+    libs: [
+        "jsr330",
+    ],
+    visibility: ["//frameworks/base/packages/SystemUI:__subpackages__"],
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUISingleton.java b/packages/SystemUI/pods/com/android/systemui/dagger/SysUISingleton.java
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/dagger/SysUISingleton.java
rename to packages/SystemUI/pods/com/android/systemui/dagger/SysUISingleton.java
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Application.java b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/Application.java
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Application.java
rename to packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/Application.java
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Background.java b/packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/Background.java
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/Background.java
rename to packages/SystemUI/pods/com/android/systemui/dagger/qualifiers/Background.java
diff --git a/packages/SystemUI/pods/com/android/systemui/retail/Android.bp b/packages/SystemUI/pods/com/android/systemui/retail/Android.bp
new file mode 100644
index 0000000..f047848
--- /dev/null
+++ b/packages/SystemUI/pods/com/android/systemui/retail/Android.bp
@@ -0,0 +1,38 @@
+//
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+soong_namespace {
+}
+
+package {
+    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+java_library {
+    name: "impl",
+    srcs: ["*.kt"],
+    libs: [
+        "jsr330",
+        "dagger2",
+        "SystemUICommon",
+        "kotlinx_coroutines",
+    ],
+    static_libs: [
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail/data:impl",
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail/domain:impl",
+    ],
+    visibility: ["//frameworks/base/packages/SystemUI"],
+}
diff --git a/packages/SystemUI/src/com/android/systemui/retail/dagger/RetailModeModule.kt b/packages/SystemUI/pods/com/android/systemui/retail/RetailModeModule.kt
similarity index 83%
rename from packages/SystemUI/src/com/android/systemui/retail/dagger/RetailModeModule.kt
rename to packages/SystemUI/pods/com/android/systemui/retail/RetailModeModule.kt
index e863949..c20e368 100644
--- a/packages/SystemUI/src/com/android/systemui/retail/dagger/RetailModeModule.kt
+++ b/packages/SystemUI/pods/com/android/systemui/retail/RetailModeModule.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.systemui.retail.dagger
+package com.android.systemui.retail
 
 import com.android.systemui.retail.data.repository.RetailModeRepository
-import com.android.systemui.retail.data.repository.RetailModeSettingsRepository
+import com.android.systemui.retail.data.repository.impl.RetailModeSettingsRepository
 import com.android.systemui.retail.domain.interactor.RetailModeInteractor
-import com.android.systemui.retail.domain.interactor.RetailModeInteractorImpl
+import com.android.systemui.retail.domain.interactor.impl.RetailModeInteractorImpl
 import dagger.Binds
 import dagger.Module
 
diff --git a/packages/SystemUI/pods/com/android/systemui/retail/data/Android.bp b/packages/SystemUI/pods/com/android/systemui/retail/data/Android.bp
new file mode 100644
index 0000000..f148a7c
--- /dev/null
+++ b/packages/SystemUI/pods/com/android/systemui/retail/data/Android.bp
@@ -0,0 +1,55 @@
+//
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+soong_namespace {
+}
+
+package {
+    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+java_library {
+    name: "api",
+    srcs: ["repository/*.kt"],
+    libs: [
+        "kotlinx_coroutines",
+    ],
+    visibility: [
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail",
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail/dagger",
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail/domain",
+    ],
+}
+
+java_library {
+    name: "impl",
+    srcs: ["repository/impl/*.kt"],
+    libs: [
+        "jsr330",
+        "kotlinx_coroutines",
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/dagger:api",
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/util/settings:api",
+        "SystemUICommon",
+    ],
+    static_libs: [
+        "api",
+    ],
+    visibility: [
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail",
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail/dagger",
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail/domain",
+    ],
+}
diff --git a/packages/SystemUI/pods/com/android/systemui/retail/data/repository/RetailModeRepository.kt b/packages/SystemUI/pods/com/android/systemui/retail/data/repository/RetailModeRepository.kt
new file mode 100644
index 0000000..c9eac25
--- /dev/null
+++ b/packages/SystemUI/pods/com/android/systemui/retail/data/repository/RetailModeRepository.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.retail.data.repository
+
+import kotlinx.coroutines.flow.StateFlow
+
+/** Repository to track if the device is in Retail mode */
+interface RetailModeRepository {
+    /** Flow of whether the device is currently in retail mode. */
+    val retailMode: StateFlow<Boolean>
+
+    /** Last value of whether the device is in retail mode. */
+    val inRetailMode: Boolean
+        get() = retailMode.value
+}
diff --git a/packages/SystemUI/src/com/android/systemui/retail/data/repository/RetailModeRepository.kt b/packages/SystemUI/pods/com/android/systemui/retail/data/repository/impl/RetailModeSettingsRepository.kt
similarity index 86%
rename from packages/SystemUI/src/com/android/systemui/retail/data/repository/RetailModeRepository.kt
rename to packages/SystemUI/pods/com/android/systemui/retail/data/repository/impl/RetailModeSettingsRepository.kt
index 09fd7df..8955263 100644
--- a/packages/SystemUI/src/com/android/systemui/retail/data/repository/RetailModeRepository.kt
+++ b/packages/SystemUI/pods/com/android/systemui/retail/data/repository/impl/RetailModeSettingsRepository.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.retail.data.repository
+package com.android.systemui.retail.data.repository.impl
 
 import android.database.ContentObserver
 import android.provider.Settings
@@ -22,6 +22,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.retail.data.repository.RetailModeRepository
 import com.android.systemui.util.settings.GlobalSettings
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
@@ -34,16 +35,6 @@
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.stateIn
 
-/** Repository to track if the device is in Retail mode */
-interface RetailModeRepository {
-    /** Flow of whether the device is currently in retail mode. */
-    val retailMode: StateFlow<Boolean>
-
-    /** Last value of whether the device is in retail mode. */
-    val inRetailMode: Boolean
-        get() = retailMode.value
-}
-
 /**
  * Tracks [Settings.Global.DEVICE_DEMO_MODE].
  *
diff --git a/packages/SystemUI/pods/com/android/systemui/retail/domain/Android.bp b/packages/SystemUI/pods/com/android/systemui/retail/domain/Android.bp
new file mode 100644
index 0000000..787861c
--- /dev/null
+++ b/packages/SystemUI/pods/com/android/systemui/retail/domain/Android.bp
@@ -0,0 +1,46 @@
+//
+// 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.
+//
+
+soong_namespace {
+}
+
+package {
+    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+java_library {
+    name: "api",
+    srcs: ["interactor/*.kt"],
+    visibility: [
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail",
+    ],
+}
+
+java_library {
+    name: "impl",
+    srcs: ["interactor/impl/*.kt"],
+    libs: [
+        "jsr330",
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/dagger:api",
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail/data:api",
+    ],
+    static_libs: [
+        "api",
+    ],
+    visibility: [
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/retail",
+    ],
+}
diff --git a/packages/SystemUI/src/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt b/packages/SystemUI/pods/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt
similarity index 65%
copy from packages/SystemUI/src/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt
copy to packages/SystemUI/pods/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt
index eea452c..748e34d 100644
--- a/packages/SystemUI/src/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt
+++ b/packages/SystemUI/pods/com/android/systemui/retail/domain/interactor/RetailModeInteractor.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.
@@ -16,22 +16,8 @@
 
 package com.android.systemui.retail.domain.interactor
 
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.retail.data.repository.RetailModeRepository
-import javax.inject.Inject
-
 /** Interactor to determine if the device is currently in retail mode */
 interface RetailModeInteractor {
     /** Whether the device is currently in retail mode */
     val isInRetailMode: Boolean
 }
-
-@SysUISingleton
-class RetailModeInteractorImpl
-@Inject
-constructor(
-    private val repository: RetailModeRepository,
-) : RetailModeInteractor {
-    override val isInRetailMode: Boolean
-        get() = repository.inRetailMode
-}
diff --git a/packages/SystemUI/src/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt b/packages/SystemUI/pods/com/android/systemui/retail/domain/interactor/impl/RetailModeInteractorImpl.kt
similarity index 79%
rename from packages/SystemUI/src/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt
rename to packages/SystemUI/pods/com/android/systemui/retail/domain/interactor/impl/RetailModeInteractorImpl.kt
index eea452c..8dbe562 100644
--- a/packages/SystemUI/src/com/android/systemui/retail/domain/interactor/RetailModeInteractor.kt
+++ b/packages/SystemUI/pods/com/android/systemui/retail/domain/interactor/impl/RetailModeInteractorImpl.kt
@@ -14,18 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.retail.domain.interactor
+package com.android.systemui.retail.domain.interactor.impl
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.retail.data.repository.RetailModeRepository
+import com.android.systemui.retail.domain.interactor.RetailModeInteractor
 import javax.inject.Inject
 
-/** Interactor to determine if the device is currently in retail mode */
-interface RetailModeInteractor {
-    /** Whether the device is currently in retail mode */
-    val isInRetailMode: Boolean
-}
-
 @SysUISingleton
 class RetailModeInteractorImpl
 @Inject
diff --git a/packages/SystemUI/pods/com/android/systemui/util/settings/Android.bp b/packages/SystemUI/pods/com/android/systemui/util/settings/Android.bp
new file mode 100644
index 0000000..1aa7729
--- /dev/null
+++ b/packages/SystemUI/pods/com/android/systemui/util/settings/Android.bp
@@ -0,0 +1,40 @@
+//
+// 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.
+//
+
+soong_namespace {
+}
+
+package {
+    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+java_library {
+    name: "api",
+    srcs: [
+        "*.java",
+        "*.kt",
+    ],
+    libs: [
+        "//frameworks/libs/systemui:tracinglib-platform",
+        "//frameworks/base/packages/SystemUI/pods/com/android/systemui/dagger:api",
+        "SystemUICommon",
+        "androidx.annotation_annotation",
+        "kotlinx_coroutines_android",
+        "jsr330",
+    ],
+    kotlincflags: ["-Xjvm-default=all"],
+    visibility: ["//frameworks/base/packages/SystemUI:__subpackages__"],
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettings.java b/packages/SystemUI/pods/com/android/systemui/util/settings/GlobalSettings.java
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettings.java
rename to packages/SystemUI/pods/com/android/systemui/util/settings/GlobalSettings.java
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java b/packages/SystemUI/pods/com/android/systemui/util/settings/GlobalSettingsImpl.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java
rename to packages/SystemUI/pods/com/android/systemui/util/settings/GlobalSettingsImpl.java
index 7fcabe4..d68501f 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java
+++ b/packages/SystemUI/pods/com/android/systemui/util/settings/GlobalSettingsImpl.java
@@ -23,7 +23,7 @@
 import android.net.Uri;
 import android.provider.Settings;
 
-import com.android.systemui.util.kotlin.SettingsSingleThreadBackground;
+import com.android.systemui.util.settings.SettingsSingleThreadBackground;
 
 import kotlinx.coroutines.CoroutineDispatcher;
 
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettings.java b/packages/SystemUI/pods/com/android/systemui/util/settings/SecureSettings.java
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/util/settings/SecureSettings.java
rename to packages/SystemUI/pods/com/android/systemui/util/settings/SecureSettings.java
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java b/packages/SystemUI/pods/com/android/systemui/util/settings/SecureSettingsImpl.java
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java
rename to packages/SystemUI/pods/com/android/systemui/util/settings/SecureSettingsImpl.java
index c296481..211a6f4 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java
+++ b/packages/SystemUI/pods/com/android/systemui/util/settings/SecureSettingsImpl.java
@@ -21,7 +21,7 @@
 import android.net.Uri;
 import android.provider.Settings;
 
-import com.android.systemui.util.kotlin.SettingsSingleThreadBackground;
+import com.android.systemui.util.settings.SettingsSingleThreadBackground;
 
 import kotlinx.coroutines.CoroutineDispatcher;
 
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt b/packages/SystemUI/pods/com/android/systemui/util/settings/SettingsProxy.kt
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
rename to packages/SystemUI/pods/com/android/systemui/util/settings/SettingsProxy.kt
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt b/packages/SystemUI/pods/com/android/systemui/util/settings/SettingsProxyExt.kt
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt
rename to packages/SystemUI/pods/com/android/systemui/util/settings/SettingsProxyExt.kt
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/SettingsSingleThreadBackground.java b/packages/SystemUI/pods/com/android/systemui/util/settings/SettingsSingleThreadBackground.java
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/util/kotlin/SettingsSingleThreadBackground.java
rename to packages/SystemUI/pods/com/android/systemui/util/settings/SettingsSingleThreadBackground.java
index e13981d..5632a36 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/SettingsSingleThreadBackground.java
+++ b/packages/SystemUI/pods/com/android/systemui/util/settings/SettingsSingleThreadBackground.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.kotlin;
+package com.android.systemui.util.settings;
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettings.java b/packages/SystemUI/pods/com/android/systemui/util/settings/SystemSettings.java
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/util/settings/SystemSettings.java
rename to packages/SystemUI/pods/com/android/systemui/util/settings/SystemSettings.java
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java b/packages/SystemUI/pods/com/android/systemui/util/settings/SystemSettingsImpl.java
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java
rename to packages/SystemUI/pods/com/android/systemui/util/settings/SystemSettingsImpl.java
index e670b2c..1b3f74e 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java
+++ b/packages/SystemUI/pods/com/android/systemui/util/settings/SystemSettingsImpl.java
@@ -21,7 +21,7 @@
 import android.net.Uri;
 import android.provider.Settings;
 
-import com.android.systemui.util.kotlin.SettingsSingleThreadBackground;
+import com.android.systemui.util.settings.SettingsSingleThreadBackground;
 
 import kotlinx.coroutines.CoroutineDispatcher;
 
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt b/packages/SystemUI/pods/com/android/systemui/util/settings/UserSettingsProxy.kt
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
rename to packages/SystemUI/pods/com/android/systemui/util/settings/UserSettingsProxy.kt
diff --git a/packages/SystemUI/res/drawable/hearing_devices_preset_spinner_background.xml b/packages/SystemUI/res/drawable/hearing_devices_spinner_background.xml
similarity index 94%
rename from packages/SystemUI/res/drawable/hearing_devices_preset_spinner_background.xml
rename to packages/SystemUI/res/drawable/hearing_devices_spinner_background.xml
index c83b6d3..dfefb9d 100644
--- a/packages/SystemUI/res/drawable/hearing_devices_preset_spinner_background.xml
+++ b/packages/SystemUI/res/drawable/hearing_devices_spinner_background.xml
@@ -14,7 +14,8 @@
     limitations under the License.
 -->
 
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
+<layer-list
+    xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:paddingMode="stack">
     <item>
@@ -30,8 +31,8 @@
         android:end="20dp"
         android:gravity="end|center_vertical">
         <vector
-            android:width="@dimen/hearing_devices_preset_spinner_arrow_size"
-            android:height="@dimen/hearing_devices_preset_spinner_arrow_size"
+            android:width="@dimen/hearing_devices_preset_spinner_icon_size"
+            android:height="@dimen/hearing_devices_preset_spinner_icon_size"
             android:viewportWidth="24"
             android:viewportHeight="24"
             android:tint="?androidprv:attr/colorControlNormal">
diff --git a/packages/SystemUI/res/drawable/hearing_devices_preset_spinner_popup_background.xml b/packages/SystemUI/res/drawable/hearing_devices_spinner_popup_background.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/hearing_devices_preset_spinner_popup_background.xml
rename to packages/SystemUI/res/drawable/hearing_devices_spinner_popup_background.xml
diff --git a/packages/SystemUI/res/drawable/hearing_devices_spinner_selected_background.xml b/packages/SystemUI/res/drawable/hearing_devices_spinner_selected_background.xml
new file mode 100644
index 0000000..c708d22
--- /dev/null
+++ b/packages/SystemUI/res/drawable/hearing_devices_spinner_selected_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+    android:drawable="@drawable/settingslib_switch_bar_bg_on"
+    android:insetTop="8dp"
+    android:insetBottom="8dp"
+    android:insetLeft="11dp"
+    android:insetRight="11dp" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_check.xml b/packages/SystemUI/res/drawable/ic_check.xml
new file mode 100644
index 0000000..80707d8
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_check.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 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.
+-->
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24"
+    android:viewportWidth="24">
+    <path
+        android:pathData="M0 0h24v24H0z"/>
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/bluetooth_device_item.xml b/packages/SystemUI/res/layout/bluetooth_device_item.xml
index b9314c7..124aec6a 100644
--- a/packages/SystemUI/res/layout/bluetooth_device_item.xml
+++ b/packages/SystemUI/res/layout/bluetooth_device_item.xml
@@ -27,8 +27,8 @@
 
     <ImageView
         android:id="@+id/bluetooth_device_icon"
-        android:layout_width="24dp"
-        android:layout_height="24dp"
+        android:layout_width="36dp"
+        android:layout_height="36dp"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
@@ -36,8 +36,8 @@
 
     <TextView
         android:layout_width="0dp"
+        android:layout_height="wrap_content"
         android:id="@+id/bluetooth_device_name"
-        style="@style/BluetoothTileDialog.DeviceName"
         android:textDirection="locale"
         android:textAlignment="gravity"
         android:paddingStart="20dp"
@@ -54,8 +54,8 @@
 
     <TextView
         android:layout_width="0dp"
+        android:layout_height="wrap_content"
         android:id="@+id/bluetooth_device_summary"
-        style="@style/BluetoothTileDialog.DeviceSummary"
         android:paddingStart="20dp"
         android:paddingEnd="10dp"
         android:paddingBottom="15dp"
@@ -65,7 +65,8 @@
         app:layout_constraintStart_toEndOf="@+id/bluetooth_device_icon"
         app:layout_constraintEnd_toStartOf="@+id/guideline"
         app:layout_constraintBottom_toBottomOf="parent"
-        android:gravity="center_vertical" />
+        android:gravity="center_vertical"
+        android:textSize="14sp" />
 
     <androidx.constraintlayout.widget.Guideline
         android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
index b4eaa40..1f93717 100644
--- a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
+++ b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
@@ -32,7 +32,7 @@
         android:ellipsize="end"
         android:gravity="center_vertical|center_horizontal"
         android:text="@string/quick_settings_bluetooth_label"
-        android:textAppearance="@style/TextAppearance.Dialog.Title"
+        android:textAppearance="@style/TextAppearance.BluetoothTileDialog"
         android:textSize="24sp"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
@@ -49,7 +49,8 @@
         android:gravity="center_vertical|center_horizontal"
         android:maxLines="2"
         android:text="@string/quick_settings_bluetooth_tile_subtitle"
-        android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
+        android:textSize="14sp"
+        android:textAppearance="@style/TextAppearance.BluetoothTileDialog"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toBottomOf="@id/bluetooth_tile_dialog_title" />
@@ -105,7 +106,7 @@
                 android:paddingStart="36dp"
                 android:text="@string/turn_on_bluetooth"
                 android:clickable="false"
-                android:textAppearance="@style/TextAppearance.Dialog.Title"
+                android:textAppearance="@style/TextAppearance.BluetoothTileDialog"
                 android:textSize="16sp"
                 app:layout_constraintEnd_toStartOf="@+id/bluetooth_toggle"
                 app:layout_constraintStart_toStartOf="parent"
@@ -146,7 +147,7 @@
                 android:paddingEnd="15dp"
                 android:paddingStart="36dp"
                 android:clickable="false"
-                android:textAppearance="@style/TextAppearance.Dialog.Title"
+                android:textAppearance="@style/TextAppearance.BluetoothTileDialog"
                 android:textSize="16sp"
                 app:layout_constraintEnd_toStartOf="@+id/bluetooth_auto_on_toggle"
                 app:layout_constraintStart_toStartOf="parent"
@@ -187,7 +188,8 @@
                 android:layout_marginTop="20dp"
                 android:paddingStart="36dp"
                 android:paddingEnd="40dp"
-                android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
+                android:textSize="14sp"
+                android:textAppearance="@style/TextAppearance.BluetoothTileDialog"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintTop_toBottomOf="@id/bluetooth_auto_on_toggle_info_icon" />
@@ -204,7 +206,7 @@
                 android:id="@+id/see_all_button"
                 style="@style/BluetoothTileDialog.Device"
                 android:paddingEnd="0dp"
-                android:paddingStart="20dp"
+                android:paddingStart="26dp"
                 android:background="@drawable/bluetooth_tile_dialog_bg_off"
                 android:layout_width="0dp"
                 android:layout_height="64dp"
@@ -214,11 +216,11 @@
                 app:layout_constraintTop_toBottomOf="@+id/device_list"
                 app:layout_constraintBottom_toTopOf="@+id/pair_new_device_button"
                 android:drawableStart="@drawable/ic_arrow_forward"
-                android:drawablePadding="20dp"
+                android:drawablePadding="26dp"
                 android:drawableTint="?android:attr/textColorPrimary"
                 android:text="@string/see_all_bluetooth_devices"
                 android:textSize="14sp"
-                android:textAppearance="@style/TextAppearance.Dialog.Title"
+                android:textAppearance="@style/TextAppearance.BluetoothTileDialog"
                 android:textDirection="locale"
                 android:textAlignment="viewStart"
                 android:maxLines="1"
@@ -229,7 +231,7 @@
                 android:id="@+id/pair_new_device_button"
                 style="@style/BluetoothTileDialog.Device"
                 android:paddingEnd="0dp"
-                android:paddingStart="20dp"
+                android:paddingStart="26dp"
                 android:background="@drawable/bluetooth_tile_dialog_bg_off"
                 android:layout_width="0dp"
                 android:layout_height="64dp"
@@ -238,11 +240,11 @@
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintTop_toBottomOf="@+id/see_all_button"
                 android:drawableStart="@drawable/ic_add"
-                android:drawablePadding="20dp"
+                android:drawablePadding="26dp"
                 android:drawableTint="?android:attr/textColorPrimary"
                 android:text="@string/pair_new_bluetooth_devices"
                 android:textSize="14sp"
-                android:textAppearance="@style/TextAppearance.Dialog.Title"
+                android:textAppearance="@style/TextAppearance.BluetoothTileDialog"
                 android:textDirection="locale"
                 android:textAlignment="viewStart"
                 android:maxLines="1"
@@ -259,6 +261,7 @@
             <Button
                 android:id="@+id/audio_sharing_button"
                 style="@style/BluetoothTileDialog.AudioSharingButton"
+                android:textAppearance="@style/TextAppearance.BluetoothTileDialog"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_marginTop="9dp"
@@ -282,6 +285,7 @@
             <Button
                 android:id="@+id/done_button"
                 style="@style/Widget.Dialog.Button"
+                android:textAppearance="@style/TextAppearance.BluetoothTileDialog"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_marginTop="9dp"
diff --git a/packages/SystemUI/res/layout/hearing_devices_preset_dropdown_item.xml b/packages/SystemUI/res/layout/hearing_devices_preset_dropdown_item.xml
deleted file mode 100644
index 17c0222..0000000
--- a/packages/SystemUI/res/layout/hearing_devices_preset_dropdown_item.xml
+++ /dev/null
@@ -1,26 +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.
--->
-
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/hearing_devices_preset_option_text"
-    style="?android:attr/spinnerDropDownItemStyle"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:minHeight="@dimen/hearing_devices_preset_spinner_height"
-    android:paddingStart="@dimen/hearing_devices_preset_spinner_text_padding_start"
-    android:gravity="center_vertical"
-    android:textDirection="locale"
-    android:ellipsize="end" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/hearing_devices_preset_spinner_selected.xml b/packages/SystemUI/res/layout/hearing_devices_preset_spinner_selected.xml
deleted file mode 100644
index d512e7c..0000000
--- a/packages/SystemUI/res/layout/hearing_devices_preset_spinner_selected.xml
+++ /dev/null
@@ -1,48 +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.
--->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:minHeight="@dimen/hearing_devices_preset_spinner_height"
-    android:paddingStart="@dimen/hearing_devices_preset_spinner_text_padding_start"
-    android:paddingTop="@dimen/hearing_devices_preset_spinner_text_padding_vertical"
-    android:paddingBottom="@dimen/hearing_devices_preset_spinner_text_padding_vertical"
-    android:orientation="vertical">
-    <TextView
-        android:layout_width="match_parent"
-        android:layout_height="0dp"
-        android:textAppearance="@style/TextAppearance.Dialog.Title"
-        android:lineSpacingExtra="6dp"
-        android:text="@string/hearing_devices_preset_label"
-        android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
-        android:textSize="14sp"
-        android:gravity="center_vertical"
-        android:textDirection="locale"
-        android:layout_weight="1" />
-    <TextView
-        android:id="@+id/hearing_devices_preset_option_text"
-        android:layout_width="match_parent"
-        android:layout_height="0dp"
-        android:textAppearance="@style/TextAppearance.Dialog.Body"
-        android:lineSpacingExtra="6dp"
-        android:gravity="center_vertical"
-        android:ellipsize="end"
-        android:maxLines="1"
-        android:textDirection="locale"
-        android:layout_weight="1" />
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/hearing_devices_spinner_dropdown_view.xml b/packages/SystemUI/res/layout/hearing_devices_spinner_dropdown_view.xml
new file mode 100644
index 0000000..70f2cd5
--- /dev/null
+++ b/packages/SystemUI/res/layout/hearing_devices_spinner_dropdown_view.xml
@@ -0,0 +1,45 @@
+<!--
+    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.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/bluetooth_dialog_device_height"
+    android:paddingStart="@dimen/hearing_devices_preset_spinner_padding"
+    android:paddingEnd="@dimen/hearing_devices_preset_spinner_padding"
+    android:orientation="horizontal">
+
+    <ImageView
+        android:id="@+id/hearing_devices_spinner_check_icon"
+        android:layout_width="@dimen/hearing_devices_preset_spinner_icon_size"
+        android:layout_height="@dimen/hearing_devices_preset_spinner_icon_size"
+        android:layout_gravity="center_vertical"
+        android:layout_marginEnd="@dimen/hearing_devices_layout_margin"
+        android:tint="?androidprv:attr/materialColorOnPrimaryContainer"
+        android:src="@drawable/ic_check"
+        android:contentDescription="@string/hearing_devices_spinner_item_selected"/>
+    <TextView
+        android:id="@+id/hearing_devices_spinner_text"
+        style="?android:attr/spinnerDropDownItemStyle"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:textDirection="locale"
+        android:paddingStart="0dp"
+        android:maxLines="1"
+        android:ellipsize="end" />
+
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/hearing_devices_spinner_view.xml b/packages/SystemUI/res/layout/hearing_devices_spinner_view.xml
new file mode 100644
index 0000000..7574244
--- /dev/null
+++ b/packages/SystemUI/res/layout/hearing_devices_spinner_view.xml
@@ -0,0 +1,32 @@
+<!--
+    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.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/bluetooth_dialog_device_height"
+    android:paddingStart="@dimen/hearing_devices_preset_spinner_padding"
+    android:paddingEnd="@dimen/hearing_devices_preset_spinner_padding">
+    <TextView
+        android:id="@+id/hearing_devices_spinner_text"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textAppearance="@style/TextAppearance.Dialog.Body"
+        android:layout_gravity="center_vertical"
+        android:ellipsize="end"
+        android:maxLines="1"
+        android:textDirection="locale" />
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml b/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml
index 80692f9..bf04a6f 100644
--- a/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml
+++ b/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml
@@ -28,50 +28,16 @@
         android:layout_height="wrap_content"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintBottom_toTopOf="@id/preset_spinner" />
-
-    <Spinner
-        android:id="@+id/preset_spinner"
-        style="@style/BluetoothTileDialog.Device"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="@dimen/hearing_devices_layout_margin"
-        android:minHeight="@dimen/hearing_devices_preset_spinner_height"
-        android:gravity="center_vertical"
-        android:background="@drawable/hearing_devices_preset_spinner_background"
-        android:popupBackground="@drawable/hearing_devices_preset_spinner_popup_background"
-        android:dropDownVerticalOffset="@dimen/hearing_devices_preset_spinner_height"
-        android:dropDownWidth="match_parent"
-        android:paddingStart="0dp"
-        android:paddingEnd="0dp"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/device_list"
-        app:layout_constraintBottom_toTopOf="@id/pair_new_device_button"
-        android:longClickable="false"
-        android:visibility="gone"/>
-
-    <androidx.constraintlayout.widget.Barrier
-        android:id="@+id/device_function_barrier"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        app:barrierAllowsGoneWidgets="false"
-        app:barrierDirection="bottom"
-        app:constraint_referenced_ids="device_list,preset_spinner" />
+        app:layout_constraintEnd_toEndOf="parent" />
 
     <Button
         android:id="@+id/pair_new_device_button"
         style="@style/BluetoothTileDialog.Device"
-        android:paddingEnd="0dp"
-        android:paddingStart="20dp"
-        android:background="@drawable/bluetooth_tile_dialog_bg_off"
-        android:layout_width="0dp"
-        android:layout_height="64dp"
-        android:contentDescription="@string/accessibility_hearing_device_pair_new_device"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/device_function_barrier"
+        app:layout_constraintTop_toBottomOf="@id/device_list"
+        android:layout_height="@dimen/bluetooth_dialog_device_height"
+        android:contentDescription="@string/accessibility_hearing_device_pair_new_device"
         android:drawableStart="@drawable/ic_add"
         android:drawablePadding="20dp"
         android:drawableTint="?android:attr/textColorPrimary"
@@ -81,26 +47,75 @@
         android:textDirection="locale"
         android:textAlignment="viewStart"
         android:maxLines="1"
-        android:ellipsize="end" />
-
-    <androidx.constraintlayout.widget.Barrier
-        android:id="@+id/device_barrier"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        app:barrierAllowsGoneWidgets="false"
-        app:barrierDirection="bottom"
-        app:constraint_referenced_ids="device_function_barrier, pair_new_device_button" />
+        android:ellipsize="end"
+        android:background="@drawable/bluetooth_tile_dialog_bg_off" />
 
     <LinearLayout
-        android:id="@+id/related_tools_container"
+        android:id="@+id/preset_layout"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_marginStart="@dimen/bluetooth_dialog_layout_margin"
-        android:layout_marginEnd="@dimen/bluetooth_dialog_layout_margin"
-        android:layout_marginTop="@dimen/hearing_devices_layout_margin"
-        android:orientation="horizontal"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/device_barrier" />
+        app:layout_constraintTop_toBottomOf="@id/pair_new_device_button"
+        android:layout_marginTop="@dimen/hearing_devices_layout_margin"
+        android:orientation="vertical">
+        <TextView
+            android:id="@+id/preset_title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="@dimen/bluetooth_dialog_layout_margin"
+            android:layout_marginEnd="@dimen/bluetooth_dialog_layout_margin"
+            android:paddingStart="@dimen/hearing_devices_small_title_padding_horizontal"
+            android:text="@string/hearing_devices_preset_label"
+            android:textAppearance="@style/TextAppearance.Dialog.Title"
+            android:textSize="14sp"
+            android:gravity="center_vertical"
+            android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+            android:textDirection="locale"/>
+        <Spinner
+            android:id="@+id/preset_spinner"
+            style="@style/BluetoothTileDialog.Device"
+            android:layout_height="@dimen/bluetooth_dialog_device_height"
+            android:layout_marginTop="4dp"
+            android:paddingStart="0dp"
+            android:paddingEnd="0dp"
+            android:background="@drawable/hearing_devices_spinner_background"
+            android:popupBackground="@drawable/hearing_devices_spinner_popup_background"
+            android:dropDownWidth="match_parent"
+            android:longClickable="false"/>
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/tools_layout"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/preset_layout"
+        android:layout_marginTop="@dimen/hearing_devices_layout_margin"
+        android:orientation="vertical">
+        <TextView
+            android:id="@+id/tools_title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="@dimen/bluetooth_dialog_layout_margin"
+            android:layout_marginEnd="@dimen/bluetooth_dialog_layout_margin"
+            android:paddingStart="@dimen/hearing_devices_small_title_padding_horizontal"
+            android:text="@string/hearing_devices_tools_label"
+            android:textAppearance="@style/TextAppearance.Dialog.Title"
+            android:textSize="14sp"
+            android:gravity="center_vertical"
+            android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+            android:textDirection="locale"/>
+        <LinearLayout
+            android:id="@+id/tools_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="@dimen/bluetooth_dialog_layout_margin"
+            android:layout_marginEnd="@dimen/bluetooth_dialog_layout_margin"
+            android:layout_marginTop="4dp"
+            android:orientation="horizontal"/>
+    </LinearLayout>
+
 
 </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/hearing_tool_item.xml b/packages/SystemUI/res/layout/hearing_tool_item.xml
index f5baf2a..da9178b 100644
--- a/packages/SystemUI/res/layout/hearing_tool_item.xml
+++ b/packages/SystemUI/res/layout/hearing_tool_item.xml
@@ -46,7 +46,7 @@
         android:textAlignment="center"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_marginTop="@dimen/hearing_devices_layout_margin"
+        android:layout_marginTop="4dp"
         android:ellipsize="end"
         android:textSize="12sp"
         android:maxLines="3"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 67eb5b0..7af0057 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1805,10 +1805,9 @@
 
     <!-- Hearing devices dialog related dimensions -->
     <dimen name="hearing_devices_layout_margin">12dp</dimen>
-    <dimen name="hearing_devices_preset_spinner_height">72dp</dimen>
-    <dimen name="hearing_devices_preset_spinner_text_padding_start">20dp</dimen>
-    <dimen name="hearing_devices_preset_spinner_text_padding_vertical">15dp</dimen>
-    <dimen name="hearing_devices_preset_spinner_arrow_size">24dp</dimen>
+    <dimen name="hearing_devices_small_title_padding_horizontal">16dp</dimen>
+    <dimen name="hearing_devices_preset_spinner_padding">22dp</dimen>
+    <dimen name="hearing_devices_preset_spinner_icon_size">24dp</dimen>
     <dimen name="hearing_devices_preset_spinner_background_radius">28dp</dimen>
     <dimen name="hearing_devices_tool_icon_size">28dp</dimen>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 53ab686..b45aadd 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1002,6 +1002,10 @@
     <string name="hearing_devices_presets_error">Couldn\'t update preset</string>
     <!-- QuickSettings: Title for hearing aids presets. Preset is a set of hearing aid settings. User can apply different settings in different environments (e.g. Outdoor, Restaurant, Home) [CHAR LIMIT=40]-->
     <string name="hearing_devices_preset_label">Preset</string>
+    <!-- QuickSettings: Content description for the icon that indicates the item is selected [CHAR LIMIT=NONE]-->
+    <string name="hearing_devices_spinner_item_selected">Selected</string>
+    <!-- QuickSettings: Title for related tools of hearing. [CHAR LIMIT=40]-->
+    <string name="hearing_devices_tools_label">Tools</string>
     <!-- QuickSettings: Tool name for hearing devices dialog related tools [CHAR LIMIT=40] [BACKUP_MESSAGE_ID=8916875614623730005]-->
     <string name="quick_settings_hearing_devices_live_caption_title">Live Caption</string>
 
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index c69b98c..e14008a 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -1481,28 +1481,14 @@
         <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
     </style>
 
-    <style name="BluetoothTileDialog.Device.Active">
-        <item name="android:textColor">?androidprv:attr/materialColorOnPrimaryContainer</item>
-    </style>
-
-    <style name="BluetoothTileDialog.DeviceName">
-        <item name="android:textSize">14sp</item>
-        <item name="android:textAppearance">@style/TextAppearance.Dialog.Title</item>
+    <style name="TextAppearance.BluetoothTileDialog">
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+        <item name="android:textDirection">locale</item>
+        <item name="android:textAlignment">gravity</item>
         <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
     </style>
 
-    <style name="BluetoothTileDialog.DeviceSummary">
-        <item name="android:ellipsize">end</item>
-        <item name="android:maxLines">2</item>
-        <item name="android:textAppearance">@style/TextAppearance.Dialog.Body.Message</item>
-        <item name="android:textColor">?androidprv:attr/materialColorOnSurfaceVariant</item>
-    </style>
-
-    <style name="BluetoothTileDialog.DeviceName.Active">
-        <item name="android:textColor">?androidprv:attr/materialColorOnPrimaryContainer</item>
-    </style>
-
-    <style name="BluetoothTileDialog.DeviceSummary.Active">
+    <style name="TextAppearance.BluetoothTileDialog.Active">
         <item name="android:textColor">?androidprv:attr/materialColorOnPrimaryContainer</item>
     </style>
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
index 1978bb8..1f21af8 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -35,10 +35,9 @@
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.view.View.Visibility;
+import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.AdapterView;
-import android.widget.ArrayAdapter;
 import android.widget.Button;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
@@ -114,10 +113,9 @@
     private SystemUIDialog mDialog;
     private RecyclerView mDeviceList;
     private List<DeviceItem> mHearingDeviceItemList;
+    private View mPresetLayout;
     private Spinner mPresetSpinner;
-    private ArrayAdapter<String> mPresetInfoAdapter;
-    private Button mPairButton;
-    private LinearLayout mRelatedToolsContainer;
+    private HearingDevicesSpinnerAdapter mPresetInfoAdapter;
     private final HearingDevicesPresetsController.PresetCallback mPresetCallback =
             new HearingDevicesPresetsController.PresetCallback() {
                 @Override
@@ -245,7 +243,7 @@
                     mPresetsController.getAllPresetInfo();
             final int activePresetIndex = mPresetsController.getActivePresetIndex();
             refreshPresetInfoAdapter(presetInfos, activePresetIndex);
-            mPresetSpinner.setVisibility(
+            mPresetLayout.setVisibility(
                     (activeHearingDevice != null && activeHearingDevice.isConnectedHapClientDevice()
                             && !mPresetInfoAdapter.isEmpty()) ? VISIBLE : GONE);
         });
@@ -291,14 +289,13 @@
         }
 
         mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_DIALOG_SHOW, mLaunchSourceId);
-        mPairButton = dialog.requireViewById(R.id.pair_new_device_button);
         mDeviceList = dialog.requireViewById(R.id.device_list);
+        mPresetLayout = dialog.requireViewById(R.id.preset_layout);
         mPresetSpinner = dialog.requireViewById(R.id.preset_spinner);
-        mRelatedToolsContainer = dialog.requireViewById(R.id.related_tools_container);
 
         setupDeviceListView(dialog);
         setupPresetSpinner(dialog);
-        setupPairNewDeviceButton(dialog, mShowPairNewDevice ? VISIBLE : GONE);
+        setupPairNewDeviceButton(dialog);
         if (com.android.systemui.Flags.hearingDevicesDialogRelatedTools()) {
             setupRelatedToolsView(dialog);
         }
@@ -353,11 +350,7 @@
                 mHearingDeviceItemList);
         mPresetsController.setHearingDeviceIfSupportHap(activeHearingDevice);
 
-        mPresetInfoAdapter = new ArrayAdapter<>(dialog.getContext(),
-                R.layout.hearing_devices_preset_spinner_selected,
-                R.id.hearing_devices_preset_option_text);
-        mPresetInfoAdapter.setDropDownViewResource(
-                R.layout.hearing_devices_preset_dropdown_item);
+        mPresetInfoAdapter = new HearingDevicesSpinnerAdapter(dialog.getContext());
         mPresetSpinner.setAdapter(mPresetInfoAdapter);
 
         // disable redundant Touch & Hold accessibility action for Switch Access
@@ -378,6 +371,7 @@
         mPresetSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
             @Override
             public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+                mPresetInfoAdapter.setSelected(position);
                 mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_PRESET_SELECT,
                         mLaunchSourceId);
                 mPresetsController.selectPreset(
@@ -389,14 +383,17 @@
                 // Do nothing
             }
         });
-        mPresetSpinner.setVisibility(
+        mPresetLayout.setVisibility(
                 (activeHearingDevice != null && activeHearingDevice.isConnectedHapClientDevice()
                         && !mPresetInfoAdapter.isEmpty()) ? VISIBLE : GONE);
     }
 
-    private void setupPairNewDeviceButton(SystemUIDialog dialog, @Visibility int visibility) {
-        if (visibility == VISIBLE) {
-            mPairButton.setOnClickListener(v -> {
+    private void setupPairNewDeviceButton(SystemUIDialog dialog) {
+        final Button pairButton = dialog.requireViewById(R.id.pair_new_device_button);
+
+        pairButton.setVisibility(mShowPairNewDevice ? VISIBLE : GONE);
+        if (mShowPairNewDevice) {
+            pairButton.setOnClickListener(v -> {
                 mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_PAIR, mLaunchSourceId);
                 dismissDialogIfExists();
                 final Intent intent = new Intent(Settings.ACTION_HEARING_DEVICE_PAIRING_SETTINGS);
@@ -404,12 +401,11 @@
                 mActivityStarter.postStartActivityDismissingKeyguard(intent, /* delay= */ 0,
                         mDialogTransitionAnimator.createActivityTransitionController(dialog));
             });
-        } else {
-            mPairButton.setVisibility(GONE);
         }
     }
 
     private void setupRelatedToolsView(SystemUIDialog dialog) {
+
         final Context context = dialog.getContext();
         final List<ToolItem> toolItemList = new ArrayList<>();
         final String[] toolNameArray;
@@ -430,15 +426,20 @@
         } catch (Resources.NotFoundException e) {
             Log.i(TAG, "No hearing devices related tool config resource");
         }
+
+        final View toolsLayout = dialog.requireViewById(R.id.tools_layout);
+        toolsLayout.setVisibility(toolItemList.isEmpty() ? GONE : VISIBLE);
+
+        final LinearLayout toolsContainer = dialog.requireViewById(R.id.tools_container);
         for (int i = 0; i < toolItemList.size(); i++) {
-            View view = createHearingToolView(context, toolItemList.get(i));
-            mRelatedToolsContainer.addView(view);
+            View view = createHearingToolView(context, toolItemList.get(i), toolsContainer);
+            toolsContainer.addView(view);
             if (i != toolItemList.size() - 1) {
                 final int spaceSize = context.getResources().getDimensionPixelSize(
                         R.dimen.hearing_devices_layout_margin);
                 Space space = new Space(context);
                 space.setLayoutParams(new LinearLayout.LayoutParams(spaceSize, 0));
-                mRelatedToolsContainer.addView(space);
+                toolsContainer.addView(space);
             }
         }
     }
@@ -453,6 +454,7 @@
             for (int position = 0; position < size; position++) {
                 if (presetInfos.get(position).getIndex() == activePresetIndex) {
                     mPresetSpinner.setSelection(position, /* animate= */ false);
+                    mPresetInfoAdapter.setSelected(position);
                 }
             }
         }
@@ -493,9 +495,9 @@
     }
 
     @NonNull
-    private View createHearingToolView(Context context, ToolItem item) {
-        View view = LayoutInflater.from(context).inflate(R.layout.hearing_tool_item,
-                mRelatedToolsContainer, false);
+    private View createHearingToolView(Context context, ToolItem item, ViewGroup container) {
+        View view = LayoutInflater.from(context).inflate(R.layout.hearing_tool_item, container,
+                false);
         ImageView icon = view.requireViewById(R.id.tool_icon);
         TextView text = view.requireViewById(R.id.tool_name);
         view.setContentDescription(item.getToolName());
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
index 9367cb5..e47e4b2 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
@@ -138,19 +138,18 @@
 
             Pair<Drawable, String> iconPair = item.getIconWithDescription();
             if (iconPair != null) {
-                Drawable drawable = iconPair.getFirst().mutate();
-                drawable.setTint(tintColor);
+                Drawable drawable = iconPair.getFirst();
                 mIconView.setImageDrawable(drawable);
                 mIconView.setContentDescription(iconPair.getSecond());
             }
 
             mNameView.setTextAppearance(
-                    item.isActive() ? R.style.BluetoothTileDialog_DeviceName_Active
-                            : R.style.BluetoothTileDialog_DeviceName);
+                    item.isActive() ? R.style.TextAppearance_BluetoothTileDialog_Active
+                            : R.style.TextAppearance_BluetoothTileDialog);
             mNameView.setText(item.getDeviceName());
             mSummaryView.setTextAppearance(
-                    item.isActive() ? R.style.BluetoothTileDialog_DeviceSummary_Active
-                            : R.style.BluetoothTileDialog_DeviceSummary);
+                    item.isActive() ? R.style.TextAppearance_BluetoothTileDialog_Active
+                            : R.style.TextAppearance_BluetoothTileDialog);
             mSummaryView.setText(item.getConnectionSummary());
 
             mGearIcon.getDrawable().mutate().setTint(tintColor);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesSpinnerAdapter.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesSpinnerAdapter.java
new file mode 100644
index 0000000..28d742c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesSpinnerAdapter.java
@@ -0,0 +1,85 @@
+/*
+ * 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.accessibility.hearingaid;
+
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.settingslib.Utils;
+import com.android.systemui.res.R;
+
+/**
+ * An ArrayAdapter which was used by Spinner in hearing devices dialog.
+ */
+public class HearingDevicesSpinnerAdapter extends ArrayAdapter<String> {
+
+    private final Context mContext;
+    private int mSelectedPosition;
+
+    public HearingDevicesSpinnerAdapter(@NonNull Context context) {
+        super(context, R.layout.hearing_devices_spinner_view,
+                R.id.hearing_devices_spinner_text);
+        setDropDownViewResource(R.layout.hearing_devices_spinner_dropdown_view);
+        mContext = context;
+    }
+
+    @Override
+    public View getDropDownView(int position, @Nullable View convertView,
+            @NonNull ViewGroup parent) {
+
+        View view = super.getDropDownView(position, convertView, parent);
+
+        final boolean isSelected = position == mSelectedPosition;
+        view.setBackgroundResource(isSelected
+                ? R.drawable.hearing_devices_spinner_selected_background
+                : R.drawable.bluetooth_tile_dialog_bg_off);
+
+        View checkIcon = view.findViewById(R.id.hearing_devices_spinner_check_icon);
+        if (checkIcon != null) {
+            checkIcon.setVisibility(isSelected ? VISIBLE : GONE);
+        }
+
+        TextView text = view.findViewById(R.id.hearing_devices_spinner_text);
+        if (text != null) {
+            int tintColor = Utils.getColorAttr(mContext,
+                    isSelected ? com.android.internal.R.attr.materialColorOnPrimaryContainer
+                            : com.android.internal.R.attr.materialColorOnSurface).getDefaultColor();
+            text.setTextColor(tintColor);
+        }
+        return view;
+    }
+
+    /**
+     * Sets the selected position into this adapter. The selected item will have different UI in
+     * the dropdown view.
+     *
+     * @param position the selected position
+     */
+    public void setSelected(int position) {
+        mSelectedPosition = position;
+        notifyDataSetChanged();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
index a9c5c69..d7a0fc9 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
@@ -100,7 +100,7 @@
             initialUiProperties: BluetoothTileDialogViewModel.UiProperties,
             cachedContentHeight: Int,
             dialogCallback: BluetoothTileDialogCallback,
-            dimissListener: Runnable
+            dimissListener: Runnable,
         ): BluetoothTileDialogDelegate
     }
 
@@ -135,7 +135,7 @@
                 object : AccessibilityDelegate() {
                     override fun onInitializeAccessibilityNodeInfo(
                         host: View,
-                        info: AccessibilityNodeInfo
+                        info: AccessibilityNodeInfo,
                     ) {
                         super.onInitializeAccessibilityNodeInfo(host, info)
                         info.addAction(
@@ -144,7 +144,7 @@
                                 context.getString(
                                     R.string
                                         .quick_settings_bluetooth_audio_sharing_button_accessibility
-                                )
+                                ),
                             )
                         )
                     }
@@ -180,7 +180,7 @@
         dialog: SystemUIDialog,
         deviceItem: List<DeviceItem>,
         showSeeAll: Boolean,
-        showPairNewDevice: Boolean
+        showPairNewDevice: Boolean,
     ) {
         withContext(mainDispatcher) {
             val start = systemClock.elapsedRealtime()
@@ -207,7 +207,7 @@
     internal fun onBluetoothStateUpdated(
         dialog: SystemUIDialog,
         isEnabled: Boolean,
-        uiProperties: BluetoothTileDialogViewModel.UiProperties
+        uiProperties: BluetoothTileDialogViewModel.UiProperties,
     ) {
         getToggleView(dialog).apply {
             isChecked = isEnabled
@@ -221,7 +221,7 @@
     internal fun onBluetoothAutoOnUpdated(
         dialog: SystemUIDialog,
         isEnabled: Boolean,
-        @StringRes infoResId: Int
+        @StringRes infoResId: Int,
     ) {
         getAutoOnToggle(dialog).isChecked = isEnabled
         getAutoOnToggleInfoTextView(dialog).text = dialog.context.getString(infoResId)
@@ -231,7 +231,7 @@
         dialog: SystemUIDialog,
         visibility: Int,
         label: String?,
-        isActive: Boolean
+        isActive: Boolean,
     ) {
         getAudioSharingButtonView(dialog).apply {
             this.visibility = visibility
@@ -339,14 +339,14 @@
             object : DiffUtil.ItemCallback<DeviceItem>() {
                 override fun areItemsTheSame(
                     deviceItem1: DeviceItem,
-                    deviceItem2: DeviceItem
+                    deviceItem2: DeviceItem,
                 ): Boolean {
                     return deviceItem1.cachedBluetoothDevice == deviceItem2.cachedBluetoothDevice
                 }
 
                 override fun areContentsTheSame(
                     deviceItem1: DeviceItem,
-                    deviceItem2: DeviceItem
+                    deviceItem2: DeviceItem,
                 ): Boolean {
                     return deviceItem1.type == deviceItem2.type &&
                         deviceItem1.cachedBluetoothDevice == deviceItem2.cachedBluetoothDevice &&
@@ -394,7 +394,7 @@
 
             internal fun bind(
                 item: DeviceItem,
-                deviceItemOnClickCallback: BluetoothTileDialogCallback
+                deviceItemOnClickCallback: BluetoothTileDialogCallback,
             ) {
                 container.apply {
                     isEnabled = item.isEnabled
@@ -409,14 +409,14 @@
                         com.android.settingslib.Utils.getColorAttr(
                                 context,
                                 if (item.isActive) InternalR.attr.materialColorOnPrimaryContainer
-                                else InternalR.attr.materialColorOnSurface
+                                else InternalR.attr.materialColorOnSurface,
                             )
                             .defaultColor
 
                     // update icons
                     iconView.apply {
                         item.iconWithDescription?.let {
-                            setImageDrawable(it.first.apply { mutate()?.setTint(tintColor) })
+                            setImageDrawable(it.first)
                             contentDescription = it.second
                         }
                     }
@@ -427,25 +427,25 @@
 
                     // update text styles
                     nameView.setTextAppearance(
-                        if (item.isActive) R.style.BluetoothTileDialog_DeviceName_Active
-                        else R.style.BluetoothTileDialog_DeviceName
+                        if (item.isActive) R.style.TextAppearance_BluetoothTileDialog_Active
+                        else R.style.TextAppearance_BluetoothTileDialog
                     )
                     summaryView.setTextAppearance(
-                        if (item.isActive) R.style.BluetoothTileDialog_DeviceSummary_Active
-                        else R.style.BluetoothTileDialog_DeviceSummary
+                        if (item.isActive) R.style.TextAppearance_BluetoothTileDialog_Active
+                        else R.style.TextAppearance_BluetoothTileDialog
                     )
 
                     accessibilityDelegate =
                         object : AccessibilityDelegate() {
                             override fun onInitializeAccessibilityNodeInfo(
                                 host: View,
-                                info: AccessibilityNodeInfo
+                                info: AccessibilityNodeInfo,
                             ) {
                                 super.onInitializeAccessibilityNodeInfo(host, info)
                                 info.addAction(
                                     AccessibilityAction(
                                         AccessibilityAction.ACTION_CLICK.id,
-                                        item.actionAccessibilityLabel
+                                        item.actionAccessibilityLabel,
                                     )
                                 )
                             }
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
index 2921373..92f0580 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
@@ -57,7 +57,6 @@
     companion object {
         @JvmStatic
         fun createDeviceItem(
-            context: Context,
             cachedDevice: CachedBluetoothDevice,
             type: DeviceItemType,
             connectionSummary: String,
@@ -71,9 +70,7 @@
                 deviceName = cachedDevice.name,
                 connectionSummary = connectionSummary,
                 iconWithDescription =
-                    BluetoothUtils.getBtClassDrawableWithDescription(context, cachedDevice).let {
-                        Pair(it.first, it.second)
-                    },
+                    cachedDevice.drawableWithDescription.let { Pair(it.first, it.second) },
                 background = background,
                 isEnabled = !cachedDevice.isBusy,
                 actionAccessibilityLabel = actionAccessibilityLabel,
@@ -96,7 +93,6 @@
 
     override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
         return createDeviceItem(
-            context,
             cachedDevice,
             DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE,
             cachedDevice.connectionSummary ?: "",
@@ -122,7 +118,6 @@
 
     override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
         return createDeviceItem(
-            context,
             cachedDevice,
             DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
             cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
@@ -153,7 +148,6 @@
 
     override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
         return createDeviceItem(
-            context,
             cachedDevice,
             DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
             context.getString(
@@ -191,7 +185,6 @@
 
     override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
         return createDeviceItem(
-            context,
             cachedDevice,
             DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
             cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
@@ -232,7 +225,6 @@
 
     override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
         return createDeviceItem(
-            context,
             cachedDevice,
             DeviceItemType.CONNECTED_BLUETOOTH_DEVICE,
             cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
@@ -262,7 +254,6 @@
 
     override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
         return createDeviceItem(
-            context,
             cachedDevice,
             DeviceItemType.SAVED_BLUETOOTH_DEVICE,
             cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 4447dff..b7d3c92 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -100,7 +100,7 @@
 import com.android.systemui.qs.footer.dagger.FooterActionsModule;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recordissue.RecordIssueModule;
-import com.android.systemui.retail.dagger.RetailModeModule;
+import com.android.systemui.retail.RetailModeModule;
 import com.android.systemui.scene.shared.model.SceneContainerConfig;
 import com.android.systemui.scene.shared.model.SceneDataSource;
 import com.android.systemui.scene.shared.model.SceneDataSourceDelegator;
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/graphics/ImageLoader.kt b/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt
index ca43871..25f9920 100644
--- a/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt
+++ b/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt
@@ -27,7 +27,6 @@
 import android.content.res.Resources.NotFoundException
 import android.graphics.Bitmap
 import android.graphics.ImageDecoder
-import android.graphics.ImageDecoder.DecodeException
 import android.graphics.drawable.AdaptiveIconDrawable
 import android.graphics.drawable.BitmapDrawable
 import android.graphics.drawable.Drawable
@@ -39,7 +38,6 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
-import java.io.IOException
 import javax.inject.Inject
 import kotlin.math.min
 import kotlinx.coroutines.CoroutineDispatcher
@@ -54,7 +52,7 @@
 @Inject
 constructor(
     @Application private val defaultContext: Context,
-    @Background private val backgroundDispatcher: CoroutineDispatcher
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
 ) {
 
     /** Source of the image data. */
@@ -103,7 +101,7 @@
         source: Source,
         @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
         @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
-        allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+        allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT,
     ): Bitmap? =
         withContext(backgroundDispatcher) { loadBitmapSync(source, maxWidth, maxHeight, allocator) }
 
@@ -127,14 +125,14 @@
         source: Source,
         @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
         @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
-        allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+        allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT,
     ): Bitmap? {
         return try {
             loadBitmapSync(
                 toImageDecoderSource(source, defaultContext),
                 maxWidth,
                 maxHeight,
-                allocator
+                allocator,
             )
         } catch (e: NotFoundException) {
             Log.w(TAG, "Couldn't load resource $source", e)
@@ -162,7 +160,7 @@
         source: ImageDecoder.Source,
         @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
         @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
-        allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+        allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT,
     ): Bitmap? =
         traceSection("ImageLoader#loadBitmap") {
             return try {
@@ -170,12 +168,11 @@
                     configureDecoderForMaximumSize(decoder, info.size, maxWidth, maxHeight)
                     decoder.allocator = allocator
                 }
-            } catch (e: IOException) {
+            } catch (e: Exception) {
+                // If we're loading an Uri, we can receive any exception from the other side.
+                // So we have to catch them all.
                 Log.w(TAG, "Failed to load source $source", e)
                 return null
-            } catch (e: DecodeException) {
-                Log.w(TAG, "Failed to decode source $source", e)
-                return null
             }
         }
 
@@ -199,7 +196,7 @@
         source: Source,
         @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
         @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
-        allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+        allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT,
     ): Drawable? =
         withContext(backgroundDispatcher) {
             loadDrawableSync(source, maxWidth, maxHeight, allocator)
@@ -227,7 +224,7 @@
         context: Context = defaultContext,
         @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
         @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
-        allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+        allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT,
     ): Drawable? =
         withContext(backgroundDispatcher) {
             loadDrawableSync(icon, context, maxWidth, maxHeight, allocator)
@@ -254,7 +251,7 @@
         source: Source,
         @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
         @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
-        allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+        allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT,
     ): Drawable? =
         traceSection("ImageLoader#loadDrawable") {
             return try {
@@ -262,7 +259,7 @@
                     toImageDecoderSource(source, defaultContext),
                     maxWidth,
                     maxHeight,
-                    allocator
+                    allocator,
                 )
                     ?:
                     // If we have a resource, retry fallback using the "normal" Resource loading
@@ -301,7 +298,7 @@
         source: ImageDecoder.Source,
         @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
         @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
-        allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+        allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT,
     ): Drawable? =
         traceSection("ImageLoader#loadDrawable") {
             return try {
@@ -309,12 +306,11 @@
                     configureDecoderForMaximumSize(decoder, info.size, maxWidth, maxHeight)
                     decoder.allocator = allocator
                 }
-            } catch (e: IOException) {
+            } catch (e: Exception) {
+                // If we're loading from an Uri, any exception can happen on the
+                // other side. We have to catch them all.
                 Log.w(TAG, "Failed to load source $source", e)
                 return null
-            } catch (e: DecodeException) {
-                Log.w(TAG, "Failed to decode source $source", e)
-                return null
             }
         }
 
@@ -325,7 +321,7 @@
         context: Context = defaultContext,
         @Px maxWidth: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
         @Px maxHeight: Int = DEFAULT_MAX_SAFE_BITMAP_SIZE_PX,
-        allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT
+        allocator: Int = ImageDecoder.ALLOCATOR_DEFAULT,
     ): Drawable? =
         traceSection("ImageLoader#loadDrawable") {
             return when (icon.type) {
@@ -341,7 +337,7 @@
                             ImageDecoder.createSource(it, icon.resId),
                             maxWidth,
                             maxHeight,
-                            allocator
+                            allocator,
                         )
                     }
                         // Fallback to non-ImageDecoder load if the attempt failed (e.g. the
@@ -360,7 +356,7 @@
                         ImageDecoder.createSource(icon.dataBytes, icon.dataOffset, icon.dataLength),
                         maxWidth,
                         maxHeight,
-                        allocator
+                        allocator,
                     )
                 }
                 else -> {
@@ -421,12 +417,10 @@
     fun loadSizeSync(source: ImageDecoder.Source): Size? {
         return try {
             ImageDecoder.decodeHeader(source).size
-        } catch (e: IOException) {
+        } catch (e: Exception) {
+            // Any exception can happen when loading Uris, so we have to catch them all.
             Log.w(TAG, "Failed to load source $source", e)
             return null
-        } catch (e: DecodeException) {
-            Log.w(TAG, "Failed to decode source $source", e)
-            return null
         }
     }
 
@@ -472,7 +466,7 @@
             decoder: ImageDecoder,
             imgSize: Size,
             @Px maxWidth: Int,
-            @Px maxHeight: Int
+            @Px maxHeight: Int,
         ) {
             if (maxWidth == DO_NOT_RESIZE && maxHeight == DO_NOT_RESIZE) {
                 return
@@ -547,7 +541,7 @@
                     pm.getApplicationInfo(
                         resPackage,
                         PackageManager.MATCH_UNINSTALLED_PACKAGES or
-                            PackageManager.GET_SHARED_LIBRARY_FILES
+                            PackageManager.GET_SHARED_LIBRARY_FILES,
                     )
                 if (ai != null) {
                     return pm.getResourcesForApplication(ai)
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/shared/model/Shortcut.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/Shortcut.kt
index 5f8570c..bf7df7e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/Shortcut.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/Shortcut.kt
@@ -20,7 +20,9 @@
     val label: String,
     val commands: List<ShortcutCommand>,
     val icon: ShortcutIcon? = null,
-)
+) {
+    val containsCustomShortcutCommands: Boolean = commands.any { it.isCustom }
+}
 
 class ShortcutBuilder(private val label: String) {
     val commands = mutableListOf<ShortcutCommand>()
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutInfo.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCustomizationRequestInfo.kt
similarity index 74%
rename from packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutInfo.kt
rename to packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCustomizationRequestInfo.kt
index e4ccc2c..203228b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCustomizationRequestInfo.kt
@@ -16,8 +16,10 @@
 
 package com.android.systemui.keyboard.shortcut.shared.model
 
-data class ShortcutInfo(
-    val label: String,
-    val categoryType: ShortcutCategoryType,
-    val subCategoryLabel: String,
-)
+sealed interface ShortcutCustomizationRequestInfo {
+    data class Add(
+        val label: String,
+        val categoryType: ShortcutCategoryType,
+        val subCategoryLabel: String,
+    ) : ShortcutCustomizationRequestInfo
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt
index 02e206e..e44bfe3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt
@@ -24,7 +24,7 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutInfo
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo
 import com.android.systemui.keyboard.shortcut.ui.composable.AssignNewShortcutDialog
 import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState
 import com.android.systemui.keyboard.shortcut.ui.viewmodel.ShortcutCustomizationViewModel
@@ -59,8 +59,8 @@
         }
     }
 
-    fun onAddShortcutDialogRequested(shortcutBeingCustomized: ShortcutInfo) {
-        viewModel.onAddShortcutDialogRequested(shortcutBeingCustomized)
+    fun onShortcutCustomizationRequested(requestInfo: ShortcutCustomizationRequestInfo) {
+        viewModel.onShortcutCustomizationRequested(requestInfo)
     }
 
     private fun createAddShortcutDialog(): Dialog {
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarter.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarter.kt
index 10a201e..fa03883 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarter.kt
@@ -84,7 +84,7 @@
                     onKeyboardSettingsClicked = { onKeyboardSettingsClicked(dialog) },
                     onSearchQueryChanged = { shortcutHelperViewModel.onSearchQueryChanged(it) },
                     onCustomizationRequested = {
-                        shortcutCustomizationDialogStarter.onAddShortcutDialogRequested(it)
+                        shortcutCustomizationDialogStarter.onShortcutCustomizationRequested(it)
                     },
                 )
                 dialog.setOnDismissListener { shortcutHelperViewModel.onViewClosed() }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index 13934ea..41e6929 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -43,6 +43,7 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.lazy.rememberLazyListState
@@ -53,6 +54,7 @@
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.automirrored.filled.OpenInNew
 import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.DeleteOutline
 import androidx.compose.material.icons.filled.ExpandMore
 import androidx.compose.material.icons.filled.Search
 import androidx.compose.material.icons.filled.Tune
@@ -71,7 +73,6 @@
 import androidx.compose.material3.TopAppBarDefaults
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -109,14 +110,15 @@
 import com.android.systemui.keyboard.shortcut.shared.model.Shortcut as ShortcutModel
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutIcon
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutInfo
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
 import com.android.systemui.keyboard.shortcut.ui.model.IconSource
 import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCategoryUi
 import com.android.systemui.keyboard.shortcut.ui.model.ShortcutsUiState
 import com.android.systemui.res.R
+import kotlinx.coroutines.delay
 
 @Composable
 fun ShortcutHelper(
@@ -125,7 +127,7 @@
     modifier: Modifier = Modifier,
     shortcutsUiState: ShortcutsUiState,
     useSinglePane: @Composable () -> Boolean = { shouldUseSinglePane() },
-    onCustomizationRequested: (ShortcutInfo) -> Unit = {},
+    onCustomizationRequested: (ShortcutCustomizationRequestInfo) -> Unit = {},
 ) {
     when (shortcutsUiState) {
         is ShortcutsUiState.Active -> {
@@ -138,6 +140,7 @@
                 onCustomizationRequested,
             )
         }
+
         else -> {
             // No-op for now.
         }
@@ -151,7 +154,7 @@
     onSearchQueryChanged: (String) -> Unit,
     modifier: Modifier,
     onKeyboardSettingsClicked: () -> Unit,
-    onCustomizationRequested: (ShortcutInfo) -> Unit = {},
+    onCustomizationRequested: (ShortcutCustomizationRequestInfo) -> Unit = {},
 ) {
     var selectedCategoryType by
         remember(shortcutsUiState.defaultSelectedCategory) {
@@ -367,14 +370,10 @@
     onCategorySelected: (ShortcutCategoryType?) -> Unit,
     onKeyboardSettingsClicked: () -> Unit,
     isShortcutCustomizerFlagEnabled: Boolean,
-    onCustomizationRequested: (ShortcutInfo) -> Unit = {},
+    onCustomizationRequested: (ShortcutCustomizationRequestInfo) -> Unit = {},
 ) {
     val selectedCategory = categories.fastFirstOrNull { it.type == selectedCategoryType }
-    var isCustomizeModeEntered by remember { mutableStateOf(false) }
-    val isCustomizing by
-        remember(isCustomizeModeEntered, isShortcutCustomizerFlagEnabled) {
-            derivedStateOf { isCustomizeModeEntered && isCustomizeModeEntered }
-        }
+    var isCustomizing by remember { mutableStateOf(false) }
 
     Column(modifier = modifier.fillMaxSize().padding(horizontal = 24.dp)) {
         Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
@@ -383,10 +382,10 @@
             }
             Spacer(modifier = Modifier.weight(1f))
             if (isShortcutCustomizerFlagEnabled) {
-                if (isCustomizeModeEntered) {
-                    DoneButton(onClick = { isCustomizeModeEntered = false })
+                if (isCustomizing) {
+                    DoneButton(onClick = { isCustomizing = false })
                 } else {
-                    CustomizeButton(onClick = { isCustomizeModeEntered = true })
+                    CustomizeButton(onClick = { isCustomizing = true })
                 }
             }
         }
@@ -441,7 +440,7 @@
     modifier: Modifier,
     category: ShortcutCategoryUi?,
     isCustomizing: Boolean,
-    onCustomizationRequested: (ShortcutInfo) -> Unit = {},
+    onCustomizationRequested: (ShortcutCustomizationRequestInfo) -> Unit = {},
 ) {
     val listState = rememberLazyListState()
     LaunchedEffect(key1 = category) { if (category != null) listState.animateScrollToItem(0) }
@@ -457,7 +456,7 @@
                 isCustomizing = isCustomizing,
                 onCustomizationRequested = { label, subCategoryLabel ->
                     onCustomizationRequested(
-                        ShortcutInfo(
+                        ShortcutCustomizationRequestInfo.Add(
                             label = label,
                             subCategoryLabel = subCategoryLabel,
                             categoryType = category.type,
@@ -565,7 +564,7 @@
             modifier = Modifier.weight(1f),
             shortcut = shortcut,
             isCustomizing = isCustomizing,
-            onAddShortcutClicked = { onCustomizationRequested(shortcut.label) },
+            onAddShortcutRequested = { onCustomizationRequested(shortcut.label) },
         )
     }
 }
@@ -594,42 +593,92 @@
     modifier: Modifier = Modifier,
     shortcut: ShortcutModel,
     isCustomizing: Boolean = false,
-    onAddShortcutClicked: () -> Unit = {},
+    onAddShortcutRequested: () -> Unit = {},
+    onDeleteShortcutRequested: () -> Unit = {},
 ) {
     FlowRow(
         modifier = modifier,
         verticalArrangement = Arrangement.spacedBy(8.dp),
+        itemVerticalAlignment = Alignment.CenterVertically,
         horizontalArrangement = Arrangement.End,
     ) {
         shortcut.commands.forEachIndexed { index, command ->
             if (index > 0) {
                 ShortcutOrSeparator(spacing = 16.dp)
             }
-            ShortcutCommand(command)
+            ShortcutCommandContainer(showBackground = command.isCustom) { ShortcutCommand(command) }
         }
         if (isCustomizing) {
             Spacer(modifier = Modifier.width(16.dp))
-            ShortcutHelperButton(
-                modifier =
-                    Modifier.border(
-                        width = 1.dp,
-                        color = MaterialTheme.colorScheme.outline,
-                        shape = CircleShape,
-                    ),
-                onClick = { onAddShortcutClicked() },
-                color = Color.Transparent,
-                width = 32.dp,
-                height = 32.dp,
-                iconSource = IconSource(imageVector = Icons.Default.Add),
-                contentColor = MaterialTheme.colorScheme.primary,
-                contentPaddingVertical = 0.dp,
-                contentPaddingHorizontal = 0.dp,
-            )
+            if (shortcut.containsCustomShortcutCommands) {
+                DeleteShortcutButton(onDeleteShortcutRequested)
+            } else {
+                AddShortcutButton(onAddShortcutRequested)
+            }
         }
     }
 }
 
 @Composable
+private fun AddShortcutButton(onClick: () -> Unit) {
+    ShortcutHelperButton(
+        modifier =
+            Modifier.border(
+                width = 1.dp,
+                color = MaterialTheme.colorScheme.outline,
+                shape = CircleShape,
+            ),
+        onClick = onClick,
+        color = Color.Transparent,
+        width = 32.dp,
+        height = 32.dp,
+        iconSource = IconSource(imageVector = Icons.Default.Add),
+        contentColor = MaterialTheme.colorScheme.primary,
+        contentPaddingVertical = 0.dp,
+        contentPaddingHorizontal = 0.dp,
+    )
+}
+
+@Composable
+private fun DeleteShortcutButton(onClick: () -> Unit) {
+    ShortcutHelperButton(
+        modifier =
+            Modifier.border(
+                width = 1.dp,
+                color = MaterialTheme.colorScheme.outline,
+                shape = CircleShape,
+            ),
+        onClick = onClick,
+        color = Color.Transparent,
+        width = 32.dp,
+        height = 32.dp,
+        iconSource = IconSource(imageVector = Icons.Default.DeleteOutline),
+        contentColor = MaterialTheme.colorScheme.primary,
+        contentPaddingVertical = 0.dp,
+        contentPaddingHorizontal = 0.dp,
+    )
+}
+
+@Composable
+private fun ShortcutCommandContainer(showBackground: Boolean, content: @Composable () -> Unit) {
+    if (showBackground) {
+        Box(
+            modifier =
+                Modifier.wrapContentSize()
+                    .background(
+                        color = MaterialTheme.colorScheme.outlineVariant,
+                        shape = RoundedCornerShape(16.dp),
+                    )
+                    .padding(4.dp)
+        ) {
+            content()
+        }
+    } else {
+        content()
+    }
+}
+
+@Composable
 private fun ShortcutCommand(command: ShortcutCommand) {
     Row {
         command.keys.forEachIndexed { keyIndex, key ->
@@ -853,7 +902,12 @@
     var queryInternal by remember { mutableStateOf("") }
     val focusRequester = remember { FocusRequester() }
     val focusManager = LocalFocusManager.current
-    LaunchedEffect(Unit) { focusRequester.requestFocus() }
+    LaunchedEffect(Unit) {
+        // TODO(b/272065229): Added minor delay so TalkBack can take focus of search box by default,
+        //  remove when default a11y focus is fixed.
+        delay(50)
+        focusRequester.requestFocus()
+    }
     SearchBar(
         modifier =
             Modifier.fillMaxWidth().focusRequester(focusRequester).onKeyEvent {
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt
index b925387..e86da5d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt
@@ -19,7 +19,7 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.ui.input.key.KeyEvent
 import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutCustomizationInteractor
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutInfo
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo
 import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
@@ -30,32 +30,35 @@
 class ShortcutCustomizationViewModel
 @AssistedInject
 constructor(private val shortcutCustomizationInteractor: ShortcutCustomizationInteractor) {
-    private val _shortcutBeingCustomized = mutableStateOf<ShortcutInfo?>(null)
+    private val _shortcutBeingCustomized = mutableStateOf<ShortcutCustomizationRequestInfo?>(null)
 
     private val _shortcutCustomizationUiState =
         MutableStateFlow<ShortcutCustomizationUiState>(ShortcutCustomizationUiState.Inactive)
 
     val shortcutCustomizationUiState = _shortcutCustomizationUiState.asStateFlow()
 
-    fun onAddShortcutDialogRequested(shortcutBeingCustomized: ShortcutInfo) {
-        _shortcutCustomizationUiState.value =
-            ShortcutCustomizationUiState.AddShortcutDialog(
-                shortcutLabel = shortcutBeingCustomized.label,
-                shouldShowErrorMessage = false,
-                isValidKeyCombination = false,
-                defaultCustomShortcutModifierKey =
-                    shortcutCustomizationInteractor.getDefaultCustomShortcutModifierKey(),
-                isDialogShowing = false,
-            )
-
-        _shortcutBeingCustomized.value = shortcutBeingCustomized
+    fun onShortcutCustomizationRequested(requestInfo: ShortcutCustomizationRequestInfo) {
+        when (requestInfo) {
+            is ShortcutCustomizationRequestInfo.Add -> {
+                _shortcutCustomizationUiState.value =
+                    ShortcutCustomizationUiState.AddShortcutDialog(
+                        shortcutLabel = requestInfo.label,
+                        shouldShowErrorMessage = false,
+                        isValidKeyCombination = false,
+                        defaultCustomShortcutModifierKey =
+                            shortcutCustomizationInteractor.getDefaultCustomShortcutModifierKey(),
+                        isDialogShowing = false,
+                    )
+                _shortcutBeingCustomized.value = requestInfo
+            }
+        }
     }
 
     fun onAddShortcutDialogShown() {
         _shortcutCustomizationUiState.update { uiState ->
-            (uiState as? ShortcutCustomizationUiState.AddShortcutDialog)
-                ?.let { it.copy(isDialogShowing = true) }
-                ?: uiState
+            (uiState as? ShortcutCustomizationUiState.AddShortcutDialog)?.copy(
+                isDialogShowing = true
+            ) ?: uiState
         }
     }
 
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/KeyguardBypassRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBypassRepository.kt
index be4ab4b..c9be207 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBypassRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBypassRepository.kt
@@ -19,7 +19,6 @@
 import android.annotation.IntDef
 import android.content.res.Resources
 import android.provider.Settings
-import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
@@ -27,13 +26,11 @@
 import com.android.systemui.keyguard.shared.model.DevicePosture
 import com.android.systemui.keyguard.shared.model.DevicePosture.UNKNOWN
 import com.android.systemui.res.R
-import com.android.systemui.tuner.TunerService
 import com.android.systemui.util.kotlin.FlowDumperImpl
+import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.callbackFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flowOf
@@ -48,7 +45,7 @@
     biometricSettingsRepository: BiometricSettingsRepository,
     devicePostureRepository: DevicePostureRepository,
     dumpManager: DumpManager,
-    private val tunerService: TunerService,
+    secureSettingsRepository: UserAwareSecureSettingsRepository,
     @Background backgroundDispatcher: CoroutineDispatcher,
 ) : FlowDumperImpl(dumpManager) {
 
@@ -61,40 +58,26 @@
         DevicePosture.toPosture(resources.getInteger(R.integer.config_face_auth_supported_posture))
     }
 
-    private val dismissByDefault: Int by lazy {
-        if (resources.getBoolean(com.android.internal.R.bool.config_faceAuthDismissesKeyguard)) {
-            1
-        } else {
-            0
-        }
-    }
-
     private var bypassEnabledSetting: Flow<Boolean> =
-        callbackFlow {
-                val updateBypassSetting = { state: Boolean ->
-                    trySendWithFailureLogging(state, TAG, "Error sending bypassSetting $state")
-                }
-
-                val tunable =
-                    TunerService.Tunable { key, _ ->
-                        updateBypassSetting(tunerService.getValue(key, dismissByDefault) != 0)
-                    }
-
-                updateBypassSetting(false)
-                tunerService.addTunable(tunable, Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD)
-                awaitClose { tunerService.removeTunable(tunable) }
-            }
+        secureSettingsRepository
+            .boolSetting(
+                name = Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD,
+                defaultValue =
+                    resources.getBoolean(
+                        com.android.internal.R.bool.config_faceAuthDismissesKeyguard
+                    ),
+            )
             .flowOn(backgroundDispatcher)
             .dumpWhileCollecting("bypassEnabledSetting")
 
-    val overrideFaceBypassSetting: Flow<Boolean> =
+    private val overrideFaceBypassSetting: Flow<Boolean> =
         when (bypassOverride) {
             FACE_UNLOCK_BYPASS_ALWAYS -> flowOf(true)
             FACE_UNLOCK_BYPASS_NEVER -> flowOf(false)
             else -> bypassEnabledSetting
         }
 
-    val isPostureAllowedForFaceAuth: Flow<Boolean> =
+    private val isPostureAllowedForFaceAuth: Flow<Boolean> =
         when (configFaceAuthSupportedPosture) {
             UNKNOWN -> flowOf(true)
             else ->
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/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index eaf8fa9..354fc3d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -305,12 +305,12 @@
     }
 
     override suspend fun forceFinishCurrentTransition() {
-        withContextMutex.lock()
-
         if (lastAnimator?.isRunning != true) {
             return
         }
 
+        withContextMutex.lock()
+
         return withContext("$TAG#forceFinishCurrentTransition", mainDispatcher) {
             withContextMutex.unlock()
 
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/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractor.kt
index 736e1a5..57a60c1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractor.kt
@@ -75,10 +75,10 @@
     override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true)
 
     private fun hasSufficientPermission(): Boolean {
-        val rotationPackage: String = packageManager.rotationResolverPackageName
-        return rotationPackage != null &&
-            packageManager.checkPermission(Manifest.permission.CAMERA, rotationPackage) ==
+        return packageManager.rotationResolverPackageName?.let {
+            packageManager.checkPermission(Manifest.permission.CAMERA, it) ==
                 PackageManager.PERMISSION_GRANTED
+        } ?: false
     }
 
     private fun isCameraRotationEnabled(
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/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutInfo.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipLogTags.kt
similarity index 67%
copy from packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutInfo.kt
copy to packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipLogTags.kt
index e4ccc2c..6c1d6c5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipLogTags.kt
@@ -14,10 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyboard.shortcut.shared.model
+package com.android.systemui.statusbar.chips
 
-data class ShortcutInfo(
-    val label: String,
-    val categoryType: ShortcutCategoryType,
-    val subCategoryLabel: String,
-)
+/** 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/collection/coordinator/DataStoreCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinator.kt
index dc8ff63..90212ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinator.kt
@@ -27,13 +27,13 @@
 import javax.inject.Inject
 
 /**
- * A small coordinator which updates the notif stack (the view layer which holds notifications)
- * with high-level data after the stack is populated with the final entries.
+ * A small coordinator which updates the notif stack (the view layer which holds notifications) with
+ * high-level data after the stack is populated with the final entries.
  */
 @CoordinatorScope
-class DataStoreCoordinator @Inject internal constructor(
-    private val notifLiveDataStoreImpl: NotifLiveDataStoreImpl
-) : CoreCoordinator {
+class DataStoreCoordinator
+@Inject
+internal constructor(private val notifLiveDataStoreImpl: NotifLiveDataStoreImpl) : CoreCoordinator {
 
     override fun attach(pipeline: NotifPipeline) {
         pipeline.addOnAfterRenderListListener { entries, _ -> onAfterRenderList(entries) }
@@ -61,4 +61,4 @@
                 }
             }
         }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
index e9292f8..32de65b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
@@ -43,8 +43,7 @@
     private val groupExpansionManagerImpl: GroupExpansionManagerImpl,
     private val renderListInteractor: RenderNotificationListInteractor,
     private val activeNotificationsInteractor: ActiveNotificationsInteractor,
-    private val sensitiveNotificationProtectionController:
-        SensitiveNotificationProtectionController,
+    private val sensitiveNotificationProtectionController: SensitiveNotificationProtectionController,
 ) : Coordinator {
 
     override fun attach(pipeline: NotifPipeline) {
@@ -52,7 +51,7 @@
         groupExpansionManagerImpl.attach(pipeline)
     }
 
-    fun onAfterRenderList(entries: List<ListEntry>, controller: NotifStackController) =
+    private fun onAfterRenderList(entries: List<ListEntry>, controller: NotifStackController) =
         traceSection("StackCoordinator.onAfterRenderList") {
             val notifStats = calculateNotifStats(entries)
             if (FooterViewRefactor.isEnabled) {
@@ -78,13 +77,13 @@
             val isSilent = section.bucket == BUCKET_SILENT
             // NOTE: NotificationEntry.isClearable will internally check group children to ensure
             //  the group itself definitively clearable.
-            val isClearable = !isSensitiveContentProtectionActive && entry.isClearable
-                    && !entry.isSensitive.value
+            val isClearable =
+                !isSensitiveContentProtectionActive && entry.isClearable && !entry.isSensitive.value
             when {
                 isSilent && isClearable -> hasClearableSilentNotifs = true
                 isSilent && !isClearable -> hasNonClearableSilentNotifs = true
                 !isSilent && isClearable -> hasClearableAlertingNotifs = true
-                !isSilent && !isClearable -> hasNonClearableAlertingNotifs = true
+                else -> hasNonClearableAlertingNotifs = true
             }
         }
         return NotifStats(
@@ -92,7 +91,7 @@
             hasNonClearableAlertingNotifs = hasNonClearableAlertingNotifs,
             hasClearableAlertingNotifs = hasClearableAlertingNotifs,
             hasNonClearableSilentNotifs = hasNonClearableSilentNotifs,
-            hasClearableSilentNotifs = hasClearableSilentNotifs
+            hasClearableSilentNotifs = hasClearableSilentNotifs,
         )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewRenderer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewRenderer.kt
index 1ea574b..410b78b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewRenderer.kt
@@ -21,15 +21,15 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 
 /**
- * This interface and the interfaces it returns define the main API surface that must be
- * implemented by the view implementation.  The term "render" is used to indicate a handoff
- * to the view system, whether that be to attach views to the hierarchy or to update independent
- * view models, data stores, or adapters.
+ * This interface and the interfaces it returns define the main API surface that must be implemented
+ * by the view implementation. The term "render" is used to indicate a handoff to the view system,
+ * whether that be to attach views to the hierarchy or to update independent view models, data
+ * stores, or adapters.
  */
 interface NotifViewRenderer {
 
     /**
-     * Hand off the list of notifications to the view implementation.  This may attach views to the
+     * Hand off the list of notifications to the view implementation. This may attach views to the
      * hierarchy or simply update an independent datastore, but once called, the implementer myst
      * also ensure that future calls to [getStackController], [getGroupController], and
      * [getRowController] will provide valid results.
@@ -37,21 +37,21 @@
     fun onRenderList(notifList: List<ListEntry>)
 
     /**
-     * Provides an interface for the pipeline to update the overall shade.
-     * This will be called at most once for each time [onRenderList] is called.
+     * Provides an interface for the pipeline to update the overall shade. This will be called at
+     * most once for each time [onRenderList] is called.
      */
     fun getStackController(): NotifStackController
 
     /**
-     * Provides an interface for the pipeline to update individual groups.
-     * This will be called at most once for each group in the most recent call to [onRenderList].
+     * Provides an interface for the pipeline to update individual groups. This will be called at
+     * most once for each group in the most recent call to [onRenderList].
      */
     fun getGroupController(group: GroupEntry): NotifGroupController
 
     /**
-     * Provides an interface for the pipeline to update individual entries.
-     * This will be called at most once for each entry in the most recent call to [onRenderList].
-     * This includes top level entries, group summaries, and group children.
+     * Provides an interface for the pipeline to update individual entries. This will be called at
+     * most once for each entry in the most recent call to [onRenderList]. This includes top level
+     * entries, group summaries, and group children.
      */
     fun getRowController(entry: NotificationEntry): NotifRowController
 
@@ -62,8 +62,8 @@
      * logic now that all data from the pipeline is known to have been set for this execution.
      *
      * When this is called, the view system can expect that no more calls will be made to the
-     * getters on this interface until after the next call to [onRenderList].  Additionally, there
+     * getters on this interface until after the next call to [onRenderList]. Additionally, there
      * should be no further calls made on the objects previously returned by those getters.
      */
     fun onDispatchComplete() {}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt
index 9b55210..9d3b098 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.collection.render
 
+import com.android.app.tracing.traceSection
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.notification.collection.GroupEntry
 import com.android.systemui.statusbar.notification.collection.ListEntry
@@ -26,7 +27,6 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderEntryListener
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderGroupListener
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderListListener
-import com.android.app.tracing.traceSection
 import javax.inject.Inject
 
 /**
@@ -77,16 +77,17 @@
         onAfterRenderEntryListeners.add(listener)
     }
 
-    override fun dumpPipeline(d: PipelineDumper) = with(d) {
-        dump("viewRenderer", viewRenderer)
-        dump("onAfterRenderListListeners", onAfterRenderListListeners)
-        dump("onAfterRenderGroupListeners", onAfterRenderGroupListeners)
-        dump("onAfterRenderEntryListeners", onAfterRenderEntryListeners)
-    }
+    override fun dumpPipeline(d: PipelineDumper) =
+        with(d) {
+            dump("viewRenderer", viewRenderer)
+            dump("onAfterRenderListListeners", onAfterRenderListListeners)
+            dump("onAfterRenderGroupListeners", onAfterRenderGroupListeners)
+            dump("onAfterRenderEntryListeners", onAfterRenderEntryListeners)
+        }
 
     private fun dispatchOnAfterRenderList(
         viewRenderer: NotifViewRenderer,
-        entries: List<ListEntry>
+        entries: List<ListEntry>,
     ) {
         traceSection("RenderStageManager.dispatchOnAfterRenderList") {
             val stackController = viewRenderer.getStackController()
@@ -98,7 +99,7 @@
 
     private fun dispatchOnAfterRenderGroups(
         viewRenderer: NotifViewRenderer,
-        entries: List<ListEntry>
+        entries: List<ListEntry>,
     ) {
         traceSection("RenderStageManager.dispatchOnAfterRenderGroups") {
             if (onAfterRenderGroupListeners.isEmpty()) {
@@ -115,7 +116,7 @@
 
     private fun dispatchOnAfterRenderEntries(
         viewRenderer: NotifViewRenderer,
-        entries: List<ListEntry>
+        entries: List<ListEntry>,
     ) {
         traceSection("RenderStageManager.dispatchOnAfterRenderEntries") {
             if (onAfterRenderEntryListeners.isEmpty()) {
@@ -131,8 +132,8 @@
     }
 
     /**
-     * Performs a forward, depth-first traversal of the list where the group's summary
-     * immediately precedes the group's children.
+     * Performs a forward, depth-first traversal of the list where the group's summary immediately
+     * precedes the group's children.
      */
     private inline fun List<ListEntry>.forEachNotificationEntry(
         action: (NotificationEntry) -> Unit
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/notification/data/repository/ActiveNotificationListRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
index 45d1034..2b9e493 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
@@ -66,7 +66,7 @@
      * Map of notification key to rank, where rank is the 0-based index of the notification on the
      * system server, meaning that in the unfiltered flattened list of notification entries.
      */
-    val rankingsMap: Map<String, Int> = emptyMap()
+    val rankingsMap: Map<String, Int> = emptyMap(),
 ) {
     operator fun get(key: Key): ActiveNotificationEntryModel? {
         return when (key) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt
index 76e228b..2c5d9c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt
@@ -38,6 +38,8 @@
     )
 
     fun resetUserExpandedStates()
+
     fun setNotificationSnoozed(sbn: StatusBarNotification, snoozeOption: SnoozeOption)
+
     fun getActiveNotificationsCount(): Int
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index 1677418..ea6a60b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -129,7 +129,7 @@
         } else {
             notificationListener.snoozeNotification(
                 sbn.key,
-                snoozeOption.minutesToSnoozeFor * 60 * 1000.toLong()
+                snoozeOption.minutesToSnoozeFor * 60 * 1000.toLong(),
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt
index 65ba6de..148b3f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt
@@ -28,9 +28,9 @@
 /**
  * Implementation of [NotificationsController] that's used when notifications rendering is disabled.
  */
-class NotificationsControllerStub @Inject constructor(
-    private val notificationListener: NotificationListener
-) : NotificationsController {
+class NotificationsControllerStub
+@Inject
+constructor(private val notificationListener: NotificationListener) : NotificationsController {
 
     override fun initialize(
         presenter: NotificationPresenter,
@@ -43,11 +43,9 @@
         notificationListener.registerAsSystemService()
     }
 
-    override fun resetUserExpandedStates() {
-    }
+    override fun resetUserExpandedStates() {}
 
-    override fun setNotificationSnoozed(sbn: StatusBarNotification, snoozeOption: SnoozeOption) {
-    }
+    override fun setNotificationSnoozed(sbn: StatusBarNotification, snoozeOption: SnoozeOption) {}
 
     override fun getActiveNotificationsCount(): Int {
         return 0
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/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/statusbar/policy/ui/dialog/viewmodel/ModeTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModeTileViewModel.kt
index abd2453..238e56a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModeTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModeTileViewModel.kt
@@ -28,7 +28,7 @@
     val icon: Icon,
     val text: String,
     val subtext: String,
-    val subtextDescription: String, // version of subtext without "on"/"off" for screen readers
+    val subtextDescription: String, // version of subtext (without "on"/"off") for screen readers
     val enabled: Boolean,
     val stateDescription: String, // "on"/"off" state of the tile, for screen readers
     val onClick: () -> Unit,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
index 4f595ed..1c13a83 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
@@ -23,6 +23,7 @@
 import android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID
 import com.android.settingslib.notification.modes.EnableZenModeDialog
 import com.android.settingslib.notification.modes.ZenMode
+import com.android.settingslib.notification.modes.ZenModeDescriptions
 import com.android.systemui.common.shared.model.asIcon
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
@@ -54,6 +55,7 @@
     private val dialogEventLogger: ModesDialogEventLogger,
 ) {
     private val zenDialogMetricsLogger = QSZenModeDialogMetricsLogger(context)
+    private val zenModeDescriptions = ZenModeDescriptions(context)
 
     // Modes that should be displayed in the dialog
     private val visibleModes: Flow<List<ZenMode>> =
@@ -92,7 +94,8 @@
                         icon = zenModeInteractor.getModeIcon(mode).drawable().asIcon(),
                         text = mode.name,
                         subtext = getTileSubtext(mode),
-                        subtextDescription = getModeDescription(mode) ?: "",
+                        subtextDescription =
+                            getModeDescription(mode, forAccessibility = true) ?: "",
                         enabled = mode.isActive,
                         stateDescription =
                             context.getString(
@@ -145,18 +148,21 @@
      * This description is used directly for the content description of a mode tile for screen
      * readers, and for the tile subtext will be augmented with the current status of the mode.
      */
-    private fun getModeDescription(mode: ZenMode): String? {
+    private fun getModeDescription(mode: ZenMode, forAccessibility: Boolean): String? {
         if (!mode.rule.isEnabled) {
             return context.resources.getString(R.string.zen_mode_set_up)
         }
         if (!mode.rule.isManualInvocationAllowed && !mode.isActive) {
             return context.resources.getString(R.string.zen_mode_no_manual_invocation)
         }
-        return mode.getDynamicDescription(context)
+        return if (forAccessibility)
+            zenModeDescriptions.getTriggerDescriptionForAccessibility(mode)
+                ?: zenModeDescriptions.getTriggerDescription(mode)
+        else zenModeDescriptions.getTriggerDescription(mode)
     }
 
     private fun getTileSubtext(mode: ZenMode): String {
-        val modeDescription = getModeDescription(mode)
+        val modeDescription = getModeDescription(mode, forAccessibility = false)
         return if (mode.isActive) {
             if (modeDescription != null) {
                 context.getString(R.string.zen_mode_on_with_details, modeDescription)
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
index 3c06828..2a9b1b9 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/SysUICoroutinesModule.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Tracing
 import com.android.systemui.dagger.qualifiers.UiBackground
+import com.android.systemui.util.settings.SettingsSingleThreadBackground
 import dagger.Module
 import dagger.Provides
 import kotlinx.coroutines.CoroutineDispatcher
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSettingsRepository.kt
index 49a0f14..af03c52 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSettingsRepository.kt
@@ -85,6 +85,12 @@
         }
     }
 
+    suspend fun setBoolean(name: String, value: Boolean) {
+        withContext(bgContext) {
+            userSettings.putBoolForUser(name, value, userRepository.getSelectedUserInfo().id)
+        }
+    }
+
     suspend fun getString(name: String): String? {
         return withContext(bgContext) {
             userSettings.getStringForUser(name, userRepository.getSelectedUserInfo().id)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
index 2ff8cbc..5bf1513 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
@@ -78,8 +78,7 @@
     fun testAvailableMediaDeviceItemFactory_createFromCachedDevice() {
         `when`(cachedDevice.name).thenReturn(DEVICE_NAME)
         `when`(cachedDevice.connectionSummary).thenReturn(CONNECTION_SUMMARY)
-        `when`(BluetoothUtils.getBtClassDrawableWithDescription(any(), any()))
-            .thenReturn(Pair.create(drawable, ""))
+        `when`(cachedDevice.drawableWithDescription).thenReturn(Pair.create(drawable, ""))
         val deviceItem = availableMediaDeviceItemFactory.create(context, cachedDevice)
 
         assertDeviceItem(deviceItem, DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE)
@@ -89,8 +88,7 @@
     fun testConnectedDeviceItemFactory_createFromCachedDevice() {
         `when`(cachedDevice.name).thenReturn(DEVICE_NAME)
         `when`(cachedDevice.connectionSummary).thenReturn(CONNECTION_SUMMARY)
-        `when`(BluetoothUtils.getBtClassDrawableWithDescription(any(), any()))
-            .thenReturn(Pair.create(drawable, ""))
+        `when`(cachedDevice.drawableWithDescription).thenReturn(Pair.create(drawable, ""))
         val deviceItem = connectedDeviceItemFactory.create(context, cachedDevice)
 
         assertDeviceItem(deviceItem, DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
@@ -100,8 +98,7 @@
     fun testSavedDeviceItemFactory_createFromCachedDevice() {
         `when`(cachedDevice.name).thenReturn(DEVICE_NAME)
         `when`(cachedDevice.connectionSummary).thenReturn(CONNECTION_SUMMARY)
-        `when`(BluetoothUtils.getBtClassDrawableWithDescription(any(), any()))
-            .thenReturn(Pair.create(drawable, ""))
+        `when`(cachedDevice.drawableWithDescription).thenReturn(Pair.create(drawable, ""))
         val deviceItem = savedDeviceItemFactory.create(context, cachedDevice)
 
         assertDeviceItem(deviceItem, DeviceItemType.SAVED_BLUETOOTH_DEVICE)
@@ -111,8 +108,7 @@
     @Test
     fun testAvailableAudioSharingMediaDeviceItemFactory_createFromCachedDevice() {
         `when`(cachedDevice.name).thenReturn(DEVICE_NAME)
-        `when`(BluetoothUtils.getBtClassDrawableWithDescription(any(), any()))
-            .thenReturn(Pair.create(drawable, ""))
+        `when`(cachedDevice.drawableWithDescription).thenReturn(Pair.create(drawable, ""))
         val deviceItem =
             AvailableAudioSharingMediaDeviceItemFactory(localBluetoothManager)
                 .create(context, cachedDevice)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderContentProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderContentProviderTest.kt
new file mode 100644
index 0000000..8d9fa6a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderContentProviderTest.kt
@@ -0,0 +1,109 @@
+/*
+ * 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.graphics
+
+import android.content.ContentProvider
+import android.content.ContentValues
+import android.content.Context
+import android.database.Cursor
+import android.net.Uri
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.rule.provider.ProviderTestRule
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+const val AUTHORITY = "exception.provider.authority"
+val TEST_URI = Uri.Builder().scheme("content").authority(AUTHORITY).path("path").build()
+
+@SmallTest
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+@RunWith(AndroidJUnit4::class)
+class ImageLoaderContentProviderTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val mockContext = mock<Context>()
+    private lateinit var imageLoader: ImageLoader
+
+    @Rule
+    @JvmField
+    @Suppress("DEPRECATION")
+    public val providerTestRule =
+        ProviderTestRule.Builder(ExceptionThrowingContentProvider::class.java, AUTHORITY).build()
+
+    @Before
+    fun setUp() {
+        whenever(mockContext.contentResolver).thenReturn(providerTestRule.resolver)
+        imageLoader = ImageLoader(mockContext, kosmos.testDispatcher)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun loadFromTestContentProvider_throwsException() {
+        // This checks if the resolution actually throws the exception from test provider.
+        mockContext.contentResolver.query(TEST_URI, null, null, null)
+    }
+
+    @Test
+    fun loadFromRuntimeExceptionThrowingProvider_returnsNull() =
+        testScope.runTest { assertThat(imageLoader.loadBitmap(ImageLoader.Uri(TEST_URI))).isNull() }
+}
+
+class ExceptionThrowingContentProvider : ContentProvider() {
+    override fun query(
+        uri: Uri,
+        projection: Array<out String>?,
+        selection: String?,
+        selectionArgs: Array<out String>?,
+        sortOrder: String?,
+    ): Cursor? {
+        throw IllegalArgumentException("Test exception")
+    }
+
+    override fun getType(uri: Uri): String? {
+        throw IllegalArgumentException("Test exception")
+    }
+
+    override fun insert(uri: Uri, values: ContentValues?): Uri? {
+        throw IllegalArgumentException("Test exception")
+    }
+
+    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
+        throw IllegalArgumentException("Test exception")
+    }
+
+    override fun update(
+        uri: Uri,
+        values: ContentValues?,
+        selection: String?,
+        selectionArgs: Array<out String>?,
+    ): Int {
+        throw IllegalArgumentException("Test exception")
+    }
+
+    override fun onCreate(): Boolean = true
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt
new file mode 100644
index 0000000..f8d8481
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.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.keyboard.shortcut.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
+import com.android.systemui.keyboard.shortcut.shortcutCustomizationViewModelFactory
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ShortcutCustomizationViewModelTest : SysuiTestCase() {
+
+    private val kosmos = Kosmos()
+    private val testScope = kosmos.testScope
+    private val viewModel = kosmos.shortcutCustomizationViewModelFactory.create()
+
+    @Test
+    fun uiState_inactiveByDefault() {
+        testScope.runTest {
+            val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+
+            assertThat(uiState).isEqualTo(ShortcutCustomizationUiState.Inactive)
+        }
+    }
+
+    @Test
+    fun uiState_correctlyUpdatedWhenAddShortcutCustomizationIsRequested() {
+        testScope.runTest {
+            viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
+            val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+
+            assertThat(uiState).isEqualTo(expectedStandardAddShortcutUiState)
+        }
+    }
+
+    @Test
+    fun uiState_consumedOnAddDialogShown() {
+        testScope.runTest {
+            val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+            viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
+            viewModel.onAddShortcutDialogShown()
+
+            assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).isDialogShowing)
+                .isTrue()
+        }
+    }
+
+    @Test
+    fun uiState_inactiveAfterDialogIsDismissed() {
+        testScope.runTest {
+            val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+            viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
+            viewModel.onAddShortcutDialogShown()
+            viewModel.onAddShortcutDialogDismissed()
+            assertThat(uiState).isEqualTo(ShortcutCustomizationUiState.Inactive)
+        }
+    }
+
+    private val standardAddShortcutRequest =
+        ShortcutCustomizationRequestInfo.Add(
+            label = "Standard shortcut",
+            categoryType = ShortcutCategoryType.System,
+            subCategoryLabel = "Standard subcategory",
+        )
+
+    private val expectedStandardAddShortcutUiState =
+        ShortcutCustomizationUiState.AddShortcutDialog(
+            shortcutLabel = "Standard shortcut",
+            shouldShowErrorMessage = false,
+            isValidKeyCombination = false,
+            defaultCustomShortcutModifierKey =
+                ShortcutKey.Icon.ResIdIcon(R.drawable.ic_ksh_key_meta),
+            isDialogShowing = false,
+        )
+}
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/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt
index e72109d..a3c5181 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt
@@ -27,16 +27,15 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderListListener
 import com.android.systemui.statusbar.notification.collection.render.NotifStackController
-import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.withArgCaptor
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.MockitoAnnotations.initMocks
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyNoMoreInteractions
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -47,14 +46,13 @@
 
     private lateinit var entry: NotificationEntry
 
-    @Mock private lateinit var pipeline: NotifPipeline
-    @Mock private lateinit var notifLiveDataStoreImpl: NotifLiveDataStoreImpl
-    @Mock private lateinit var stackController: NotifStackController
-    @Mock private lateinit var section: NotifSection
+    private val pipeline: NotifPipeline = mock()
+    private val notifLiveDataStoreImpl: NotifLiveDataStoreImpl = mock()
+    private val stackController: NotifStackController = mock()
+    private val section: NotifSection = mock()
 
     @Before
     fun setUp() {
-        initMocks(this)
         entry = NotificationEntryBuilder().setSection(section).build()
         coordinator = DataStoreCoordinator(notifLiveDataStoreImpl)
         coordinator.attach(pipeline)
@@ -76,31 +74,35 @@
             listOf(
                 notificationEntry("foo", 1),
                 notificationEntry("foo", 2),
-                GroupEntryBuilder().setSummary(
-                    notificationEntry("bar", 1)
-                ).setChildren(
-                    listOf(
-                        notificationEntry("bar", 2),
-                        notificationEntry("bar", 3),
-                        notificationEntry("bar", 4)
+                GroupEntryBuilder()
+                    .setSummary(notificationEntry("bar", 1))
+                    .setChildren(
+                        listOf(
+                            notificationEntry("bar", 2),
+                            notificationEntry("bar", 3),
+                            notificationEntry("bar", 4),
+                        )
                     )
-                ).setSection(section).build(),
-                notificationEntry("baz", 1)
+                    .setSection(section)
+                    .build(),
+                notificationEntry("baz", 1),
             ),
-            stackController
+            stackController,
         )
         val list: List<NotificationEntry> = withArgCaptor {
             verify(notifLiveDataStoreImpl).setActiveNotifList(capture())
         }
-        assertThat(list.map { it.key }).containsExactly(
-            "0|foo|1|null|0",
-            "0|foo|2|null|0",
-            "0|bar|1|null|0",
-            "0|bar|2|null|0",
-            "0|bar|3|null|0",
-            "0|bar|4|null|0",
-            "0|baz|1|null|0"
-        ).inOrder()
+        assertThat(list.map { it.key })
+            .containsExactly(
+                "0|foo|1|null|0",
+                "0|foo|2|null|0",
+                "0|bar|1|null|0",
+                "0|bar|2|null|0",
+                "0|bar|3|null|0",
+                "0|bar|4|null|0",
+                "0|baz|1|null|0",
+            )
+            .inOrder()
         verifyNoMoreInteractions(notifLiveDataStoreImpl)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
index 56b70bd..2c37f51 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
@@ -38,41 +38,37 @@
 import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
 import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
 import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.withArgCaptor
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.MockitoAnnotations.initMocks
-import org.mockito.Mockito.`when` as whenever
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyNoMoreInteractions
+import org.mockito.kotlin.whenever
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 @RunWithLooper
 class StackCoordinatorTest : SysuiTestCase() {
+    private lateinit var entry: NotificationEntry
     private lateinit var coordinator: StackCoordinator
     private lateinit var afterRenderListListener: OnAfterRenderListListener
 
-    private lateinit var entry: NotificationEntry
-
-    @Mock private lateinit var pipeline: NotifPipeline
-    @Mock private lateinit var groupExpansionManagerImpl: GroupExpansionManagerImpl
-    @Mock private lateinit var renderListInteractor: RenderNotificationListInteractor
-    @Mock private lateinit var activeNotificationsInteractor: ActiveNotificationsInteractor
-    @Mock
-    private lateinit var sensitiveNotificationProtectionController:
-        SensitiveNotificationProtectionController
-    @Mock private lateinit var stackController: NotifStackController
-    @Mock private lateinit var section: NotifSection
-    @Mock private lateinit var row: ExpandableNotificationRow
+    private val pipeline: NotifPipeline = mock()
+    private val groupExpansionManagerImpl: GroupExpansionManagerImpl = mock()
+    private val renderListInteractor: RenderNotificationListInteractor = mock()
+    private val activeNotificationsInteractor: ActiveNotificationsInteractor = mock()
+    private val sensitiveNotificationProtectionController:
+        SensitiveNotificationProtectionController =
+        mock()
+    private val stackController: NotifStackController = mock()
+    private val section: NotifSection = mock()
+    private val row: ExpandableNotificationRow = mock()
 
     @Before
     fun setUp() {
-        initMocks(this)
-
         whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(false)
 
         entry = NotificationEntryBuilder().setSection(section).build()
@@ -86,9 +82,9 @@
                 sensitiveNotificationProtectionController,
             )
         coordinator.attach(pipeline)
-        afterRenderListListener = withArgCaptor {
-            verify(pipeline).addOnAfterRenderListListener(capture())
-        }
+        val captor = argumentCaptor<OnAfterRenderListListener>()
+        verify(pipeline).addOnAfterRenderListListener(captor.capture())
+        afterRenderListListener = captor.lastValue
     }
 
     @Test
@@ -109,7 +105,16 @@
     fun testSetNotificationStats_clearableAlerting() {
         whenever(section.bucket).thenReturn(BUCKET_ALERTING)
         afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
-        verify(stackController).setNotifStats(NotifStats(1, false, true, false, false))
+        verify(stackController)
+            .setNotifStats(
+                NotifStats(
+                    1,
+                    hasNonClearableAlertingNotifs = false,
+                    hasClearableAlertingNotifs = true,
+                    hasNonClearableSilentNotifs = false,
+                    hasClearableSilentNotifs = false,
+                )
+            )
         verifyNoMoreInteractions(activeNotificationsInteractor)
     }
 
@@ -120,7 +125,16 @@
         whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
         whenever(section.bucket).thenReturn(BUCKET_ALERTING)
         afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
-        verify(stackController).setNotifStats(NotifStats(1, true, false, false, false))
+        verify(stackController)
+            .setNotifStats(
+                NotifStats(
+                    1,
+                    hasNonClearableAlertingNotifs = true,
+                    hasClearableAlertingNotifs = false,
+                    hasNonClearableSilentNotifs = false,
+                    hasClearableSilentNotifs = false,
+                )
+            )
         verifyNoMoreInteractions(activeNotificationsInteractor)
     }
 
@@ -129,7 +143,16 @@
     fun testSetNotificationStats_clearableSilent() {
         whenever(section.bucket).thenReturn(BUCKET_SILENT)
         afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
-        verify(stackController).setNotifStats(NotifStats(1, false, false, false, true))
+        verify(stackController)
+            .setNotifStats(
+                NotifStats(
+                    1,
+                    hasNonClearableAlertingNotifs = false,
+                    hasClearableAlertingNotifs = false,
+                    hasNonClearableSilentNotifs = false,
+                    hasClearableSilentNotifs = true,
+                )
+            )
         verifyNoMoreInteractions(activeNotificationsInteractor)
     }
 
@@ -140,7 +163,16 @@
         whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
         whenever(section.bucket).thenReturn(BUCKET_SILENT)
         afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
-        verify(stackController).setNotifStats(NotifStats(1, false, false, true, false))
+        verify(stackController)
+            .setNotifStats(
+                NotifStats(
+                    1,
+                    hasNonClearableAlertingNotifs = false,
+                    hasClearableAlertingNotifs = false,
+                    hasNonClearableSilentNotifs = true,
+                    hasClearableSilentNotifs = false,
+                )
+            )
         verifyNoMoreInteractions(activeNotificationsInteractor)
     }
 
@@ -150,7 +182,15 @@
         whenever(section.bucket).thenReturn(BUCKET_ALERTING)
         afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
         verify(activeNotificationsInteractor)
-            .setNotifStats(NotifStats(1, false, true, false, false))
+            .setNotifStats(
+                NotifStats(
+                    1,
+                    hasNonClearableAlertingNotifs = false,
+                    hasClearableAlertingNotifs = true,
+                    hasNonClearableSilentNotifs = false,
+                    hasClearableSilentNotifs = false,
+                )
+            )
         verifyNoMoreInteractions(stackController)
     }
 
@@ -158,14 +198,22 @@
     @EnableFlags(
         FooterViewRefactor.FLAG_NAME,
         FLAG_SCREENSHARE_NOTIFICATION_HIDING,
-        FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX
+        FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX,
     )
     fun testSetNotificationStats_footerFlagOn_isSensitiveStateActive_nonClearableAlerting() {
         whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
         whenever(section.bucket).thenReturn(BUCKET_ALERTING)
         afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
         verify(activeNotificationsInteractor)
-            .setNotifStats(NotifStats(1, true, false, false, false))
+            .setNotifStats(
+                NotifStats(
+                    1,
+                    hasNonClearableAlertingNotifs = true,
+                    hasClearableAlertingNotifs = false,
+                    hasNonClearableSilentNotifs = false,
+                    hasClearableSilentNotifs = false,
+                )
+            )
         verifyNoMoreInteractions(stackController)
     }
 
@@ -175,7 +223,15 @@
         whenever(section.bucket).thenReturn(BUCKET_SILENT)
         afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
         verify(activeNotificationsInteractor)
-            .setNotifStats(NotifStats(1, false, false, false, true))
+            .setNotifStats(
+                NotifStats(
+                    1,
+                    hasNonClearableAlertingNotifs = false,
+                    hasClearableAlertingNotifs = false,
+                    hasNonClearableSilentNotifs = false,
+                    hasClearableSilentNotifs = true,
+                )
+            )
         verifyNoMoreInteractions(stackController)
     }
 
@@ -183,27 +239,41 @@
     @EnableFlags(
         FooterViewRefactor.FLAG_NAME,
         FLAG_SCREENSHARE_NOTIFICATION_HIDING,
-        FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX
+        FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX,
     )
     fun testSetNotificationStats_footerFlagOn_isSensitiveStateActive_nonClearableSilent() {
         whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
         whenever(section.bucket).thenReturn(BUCKET_SILENT)
         afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
         verify(activeNotificationsInteractor)
-            .setNotifStats(NotifStats(1, false, false, true, false))
+            .setNotifStats(
+                NotifStats(
+                    1,
+                    hasNonClearableAlertingNotifs = false,
+                    hasClearableAlertingNotifs = false,
+                    hasNonClearableSilentNotifs = true,
+                    hasClearableSilentNotifs = false,
+                )
+            )
         verifyNoMoreInteractions(stackController)
     }
 
     @Test
-    @EnableFlags(
-        FooterViewRefactor.FLAG_NAME
-    )
+    @EnableFlags(FooterViewRefactor.FLAG_NAME)
     fun testSetNotificationStats_footerFlagOn_nonClearableRedacted() {
         entry.setSensitive(true, true)
         whenever(section.bucket).thenReturn(BUCKET_ALERTING)
         afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
         verify(activeNotificationsInteractor)
-            .setNotifStats(NotifStats(1, hasNonClearableAlertingNotifs = true, false, false, false))
+            .setNotifStats(
+                NotifStats(
+                    1,
+                    hasNonClearableAlertingNotifs = true,
+                    hasClearableAlertingNotifs = false,
+                    hasNonClearableSilentNotifs = false,
+                    hasClearableSilentNotifs = false,
+                )
+            )
         verifyNoMoreInteractions(stackController)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/data/repository/KeyguardBypassRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/data/repository/KeyguardBypassRepositoryTest.kt
index 0c0b5ba..a2fabf3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/data/repository/KeyguardBypassRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/data/repository/KeyguardBypassRepositoryTest.kt
@@ -34,45 +34,27 @@
 import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN
 import com.android.systemui.statusbar.policy.devicePostureController
 import com.android.systemui.testKosmos
-import com.android.systemui.tuner.TunerService
-import com.android.systemui.tuner.tunerService
-import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.settings.data.repository.userAwareSecureSettingsRepository
 import com.google.common.truth.Truth.assertThat
 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.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.ArgumentMatchers.eq
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-import org.mockito.junit.MockitoJUnit
-import org.mockito.junit.MockitoRule
-import org.mockito.kotlin.whenever
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 @EnableSceneContainer
 class KeyguardBypassRepositoryTest : SysuiTestCase() {
-    @JvmField @Rule val mockito: MockitoRule = MockitoJUnit.rule()
 
-    private lateinit var tunableCallback: TunerService.Tunable
     private lateinit var postureControllerCallback: DevicePostureController.Callback
 
     private val kosmos = testKosmos()
     private lateinit var underTest: KeyguardBypassRepository
     private val testScope = kosmos.testScope
 
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-    }
-
     // overrideFaceBypassSetting overridden to true
     // isFaceEnrolledAndEnabled true
     // isPostureAllowedForFaceAuth true/false on posture changes
@@ -148,24 +130,25 @@
             val bypassEnabled by collectLastValue(underTest.isBypassAvailable)
             runCurrent()
             postureControllerCallback = kosmos.devicePostureController.verifyCallback()
-            tunableCallback = kosmos.tunerService.captureCallback()
 
             // Update face auth posture to match config
             postureControllerCallback.onPostureChanged(DEVICE_POSTURE_CLOSED)
 
             // FACE_UNLOCK_DISMISSES_KEYGUARD setting true
-            whenever(kosmos.tunerService.getValue(eq(faceUnlockDismissesKeyguard), anyInt()))
-                .thenReturn(1)
-            tunableCallback.onTuningChanged(faceUnlockDismissesKeyguard, "")
+            kosmos.userAwareSecureSettingsRepository.setBoolean(
+                Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD,
+                true,
+            )
 
             runCurrent()
             // Assert bypass enabled
             assertThat(bypassEnabled).isTrue()
 
             // FACE_UNLOCK_DISMISSES_KEYGUARD setting false
-            whenever(kosmos.tunerService.getValue(eq(faceUnlockDismissesKeyguard), anyInt()))
-                .thenReturn(0)
-            tunableCallback.onTuningChanged(faceUnlockDismissesKeyguard, "")
+            kosmos.userAwareSecureSettingsRepository.setBoolean(
+                Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD,
+                false,
+            )
 
             runCurrent()
             // Assert bypass not enabled
@@ -229,10 +212,3 @@
         private const val FACE_UNLOCK_BYPASS_NEVER = 2
     }
 }
-
-private const val faceUnlockDismissesKeyguard = Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD
-
-private fun TunerService.captureCallback() =
-    withArgCaptor<TunerService.Tunable> {
-        verify(this@captureCallback).addTunable(capture(), eq(faceUnlockDismissesKeyguard))
-    }
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/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/data/repository/KeyguardBypassRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBypassRepositoryKosmos.kt
index c91823c..0de456b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBypassRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBypassRepositoryKosmos.kt
@@ -25,8 +25,8 @@
 import com.android.systemui.statusbar.policy.DevicePostureController
 import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED
 import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN
-import com.android.systemui.tuner.tunerService
 import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.settings.data.repository.userAwareSecureSettingsRepository
 import org.mockito.ArgumentMatchers.any
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
@@ -37,7 +37,7 @@
         biometricSettingsRepository,
         devicePostureRepository,
         dumpManager,
-        tunerService,
+        userAwareSecureSettingsRepository,
         testDispatcher,
     )
 }
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..3cd613b 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.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,6 +119,7 @@
     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 }
@@ -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/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index b221d74..827e3ef 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -607,7 +607,8 @@
                     // ... and see if these are hosts we've been awaiting.
                     // NOTE: We are backing up and restoring only the owner.
                     // TODO: http://b/22388012
-                    if (newPackageAdded && userId == UserHandle.USER_SYSTEM) {
+                    UserHandle mainUser = mUserManager.getMainUser();
+                    if (newPackageAdded && mainUser != null && userId == mainUser.getIdentifier()) {
                         final int uid = getUidForPackage(pkgName, userId);
                         if (uid >= 0 ) {
                             resolveHostUidLocked(pkgName, uid);
diff --git a/services/core/java/com/android/server/am/BroadcastController.java b/services/core/java/com/android/server/am/BroadcastController.java
index 8a12858..446b367 100644
--- a/services/core/java/com/android/server/am/BroadcastController.java
+++ b/services/core/java/com/android/server/am/BroadcastController.java
@@ -57,7 +57,6 @@
 import android.app.ApplicationThreadConstants;
 import android.app.BackgroundStartPrivileges;
 import android.app.BroadcastOptions;
-import android.app.BroadcastStickyCache;
 import android.app.IApplicationThread;
 import android.app.compat.CompatChanges;
 import android.appwidget.AppWidgetManager;
@@ -702,7 +701,6 @@
             boolean serialized, boolean sticky, int userId) {
         mService.enforceNotIsolatedCaller("broadcastIntent");
 
-        int result;
         synchronized (mService) {
             intent = verifyBroadcastLocked(intent);
 
@@ -724,7 +722,7 @@
 
             final long origId = Binder.clearCallingIdentity();
             try {
-                result = broadcastIntentLocked(callerApp,
+                return broadcastIntentLocked(callerApp,
                         callerApp != null ? callerApp.info.packageName : null, callingFeatureId,
                         intent, resolvedType, resultToApp, resultTo, resultCode, resultData,
                         resultExtras, requiredPermissions, excludedPermissions, excludedPackages,
@@ -735,10 +733,6 @@
                 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
             }
         }
-        if (sticky && result == ActivityManager.BROADCAST_SUCCESS) {
-            BroadcastStickyCache.incrementVersion(intent.getAction());
-        }
-        return result;
     }
 
     // Not the binder call surface
@@ -749,7 +743,6 @@
             boolean serialized, boolean sticky, int userId,
             BackgroundStartPrivileges backgroundStartPrivileges,
             @Nullable int[] broadcastAllowList) {
-        int result;
         synchronized (mService) {
             intent = verifyBroadcastLocked(intent);
 
@@ -757,7 +750,7 @@
             String[] requiredPermissions = requiredPermission == null ? null
                     : new String[] {requiredPermission};
             try {
-                result = broadcastIntentLocked(null, packageName, featureId, intent, resolvedType,
+                return broadcastIntentLocked(null, packageName, featureId, intent, resolvedType,
                         resultToApp, resultTo, resultCode, resultData, resultExtras,
                         requiredPermissions, null, null, OP_NONE, bOptions, serialized, sticky, -1,
                         uid, realCallingUid, realCallingPid, userId,
@@ -767,10 +760,6 @@
                 Binder.restoreCallingIdentity(origId);
             }
         }
-        if (sticky && result == ActivityManager.BROADCAST_SUCCESS) {
-            BroadcastStickyCache.incrementVersion(intent.getAction());
-        }
-        return result;
     }
 
     @GuardedBy("mService")
@@ -1469,7 +1458,6 @@
                     list.add(StickyBroadcast.create(new Intent(intent), deferUntilActive,
                             callingUid, callerAppProcessState, resolvedType));
                 }
-                BroadcastStickyCache.incrementVersion(intent.getAction());
             }
         }
 
@@ -1736,7 +1724,6 @@
             Slog.w(TAG, msg);
             throw new SecurityException(msg);
         }
-        final ArrayList<String> changedStickyBroadcasts = new ArrayList<>();
         synchronized (mStickyBroadcasts) {
             ArrayMap<String, ArrayList<StickyBroadcast>> stickies = mStickyBroadcasts.get(userId);
             if (stickies != null) {
@@ -1753,16 +1740,12 @@
                     if (list.size() <= 0) {
                         stickies.remove(intent.getAction());
                     }
-                    changedStickyBroadcasts.add(intent.getAction());
                 }
                 if (stickies.size() <= 0) {
                     mStickyBroadcasts.remove(userId);
                 }
             }
         }
-        for (int i = changedStickyBroadcasts.size() - 1; i >= 0; --i) {
-            BroadcastStickyCache.incrementVersionIfExists(changedStickyBroadcasts.get(i));
-        }
     }
 
     void finishReceiver(IBinder caller, int resultCode, String resultData,
@@ -1925,9 +1908,7 @@
 
     private void sendPackageBroadcastLocked(int cmd, String[] packages, int userId) {
         mService.mProcessList.sendPackageBroadcastLocked(cmd, packages, userId);
-    }
-
-    private List<ResolveInfo> collectReceiverComponents(
+    }private List<ResolveInfo> collectReceiverComponents(
             Intent intent, String resolvedType, int callingUid, int callingPid,
             int[] users, int[] broadcastAllowList) {
         // TODO: come back and remove this assumption to triage all broadcasts
@@ -2143,18 +2124,9 @@
     }
 
     void removeStickyBroadcasts(int userId) {
-        final ArrayList<String> changedStickyBroadcasts = new ArrayList<>();
         synchronized (mStickyBroadcasts) {
-            final ArrayMap<String, ArrayList<StickyBroadcast>> stickies =
-                    mStickyBroadcasts.get(userId);
-            if (stickies != null) {
-                changedStickyBroadcasts.addAll(stickies.keySet());
-            }
             mStickyBroadcasts.remove(userId);
         }
-        for (int i = changedStickyBroadcasts.size() - 1; i >= 0; --i) {
-            BroadcastStickyCache.incrementVersionIfExists(changedStickyBroadcasts.get(i));
-        }
     }
 
     @NeverCompile
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 3dd5ec9..ef5296e 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -221,6 +221,7 @@
         "preload_safety",
         "printing",
         "privacy_infra_policy",
+        "psap_ai",
         "ravenwood",
         "resource_manager",
         "responsible_apis",
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/OWNERS b/services/core/java/com/android/server/display/OWNERS
index 9439eaa..9f0cabf 100644
--- a/services/core/java/com/android/server/display/OWNERS
+++ b/services/core/java/com/android/server/display/OWNERS
@@ -1,5 +1,4 @@
 michaelwr@google.com
-dangittik@google.com
 hackbod@google.com
 ogunwale@google.com
 santoscordon@google.com
@@ -7,5 +6,6 @@
 wilczynskip@google.com
 brup@google.com
 petsjonkin@google.com
+olb@google.com
 
 per-file ColorDisplayService.java=christyfranks@google.com
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 7850360..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"
@@ -454,3 +462,11 @@
     bug: "354059797"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "enable_display_content_mode_management"
+    namespace: "lse_desktop_experience"
+    description: "Enable switching the content mode of connected displays between mirroring and extened. Also change the default content mode to extended mode."
+    bug: "378385869"
+    is_fixed_read_only: true
+}
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/location/contexthub/OWNERS b/services/core/java/com/android/server/location/contexthub/OWNERS
index c62e323..6536ca0 100644
--- a/services/core/java/com/android/server/location/contexthub/OWNERS
+++ b/services/core/java/com/android/server/location/contexthub/OWNERS
@@ -1,3 +1,4 @@
 bduddie@google.com
+arthuri@google.com
 matthewsedam@google.com
 stange@google.com
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java
index 84413d5..c5c8a5e 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityService.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -96,7 +96,35 @@
 
         @Override
         public PictureProfile getPictureProfile(int type, String name) {
-            return null;
+            SQLiteDatabase db = mMediaQualityDbHelper.getReadableDatabase();
+
+            String selection = PictureQuality.PARAMETER_TYPE + " = ? AND "
+                    + PictureQuality.PARAMETER_NAME + " = ?";
+            String[] selectionArguments = {Integer.toString(type), name};
+
+            try (
+                    Cursor cursor = db.query(
+                            mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME,
+                            getAllPictureProfileColumns(),
+                            selection,
+                            selectionArguments,
+                            /*groupBy=*/ null,
+                            /*having=*/ null,
+                            /*orderBy=*/ null)
+            ) {
+                int count = cursor.getCount();
+                if (count == 0) {
+                    return null;
+                }
+                if (count > 1) {
+                    Log.wtf(TAG, String.format(Locale.US, "%d entries found for type=%d and name=%s"
+                                    + " in %s. Should only ever be 0 or 1.", count, type, name,
+                                    mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME));
+                    return null;
+                }
+                cursor.moveToFirst();
+                return getPictureProfileFromCursor(cursor);
+            }
         }
 
         private String bundleToJson(Bundle bundle) {
@@ -145,17 +173,79 @@
             return bundle;
         }
 
+        private String[] getAllPictureProfileColumns() {
+            return new String[]{
+                    PictureQuality.PARAMETER_ID,
+                    PictureQuality.PARAMETER_TYPE,
+                    PictureQuality.PARAMETER_NAME,
+                    PictureQuality.PARAMETER_INPUT_ID,
+                    PictureQuality.PARAMETER_PACKAGE,
+                    mMediaQualityDbHelper.SETTINGS
+            };
+        }
+
+        private PictureProfile getPictureProfileFromCursor(Cursor cursor) {
+            String returnId = cursor.getString(
+                    cursor.getColumnIndexOrThrow(PictureQuality.PARAMETER_ID));
+            int type = cursor.getInt(
+                    cursor.getColumnIndexOrThrow(PictureQuality.PARAMETER_TYPE));
+            String name = cursor.getString(
+                    cursor.getColumnIndexOrThrow(PictureQuality.PARAMETER_NAME));
+            String inputId = cursor.getString(
+                    cursor.getColumnIndexOrThrow(PictureQuality.PARAMETER_INPUT_ID));
+            String packageName = cursor.getString(
+                    cursor.getColumnIndexOrThrow(PictureQuality.PARAMETER_PACKAGE));
+            String settings = cursor.getString(
+                    cursor.getColumnIndexOrThrow(mMediaQualityDbHelper.SETTINGS));
+            return new PictureProfile(returnId, type, name, inputId,
+                    packageName, jsonToBundle(settings));
+        }
+
         @Override
         public List<PictureProfile> getPictureProfilesByPackage(String packageName) {
-            return new ArrayList<>();
+            String selection = PictureQuality.PARAMETER_PACKAGE + " = ?";
+            String[] selectionArguments = {packageName};
+            return getPictureProfilesBasedOnConditions(getAllPictureProfileColumns(), selection,
+                    selectionArguments);
         }
+
         @Override
         public List<PictureProfile> getAvailablePictureProfiles() {
             return new ArrayList<>();
         }
+
         @Override
         public List<String> getPictureProfilePackageNames() {
-            return new ArrayList<>();
+            String [] column = {PictureQuality.PARAMETER_NAME};
+            List<PictureProfile> pictureProfiles = getPictureProfilesBasedOnConditions(column,
+                    null, null);
+            List<String> packageNames = new ArrayList<>();
+            for (PictureProfile pictureProfile: pictureProfiles) {
+                packageNames.add(pictureProfile.getName());
+            }
+            return packageNames;
+        }
+
+        private List<PictureProfile> getPictureProfilesBasedOnConditions(String[] columns,
+                String selection, String[] selectionArguments) {
+            SQLiteDatabase db = mMediaQualityDbHelper.getReadableDatabase();
+
+            try (
+                    Cursor cursor = db.query(
+                            mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME,
+                            columns,
+                            selection,
+                            selectionArguments,
+                            /*groupBy=*/ null,
+                            /*having=*/ null,
+                            /*orderBy=*/ null)
+            ) {
+                List<PictureProfile> pictureProfiles = new ArrayList<>();
+                while (cursor.moveToNext()) {
+                    pictureProfiles.add(getPictureProfileFromCursor(cursor));
+                }
+                return pictureProfiles;
+            }
         }
 
         @Override
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 207764b..0d8880a 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -11937,16 +11937,19 @@
                     TrimCache trimCache = new TrimCache(sbn);
                     final INotificationListener assistant = (INotificationListener) info.service;
                     final StatusBarNotification sbnToPost = trimCache.ForListener(info);
-                    final StatusBarNotificationHolder sbnHolder =
-                            new StatusBarNotificationHolder(sbnToPost);
+                    final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
+
                     try {
-                        if (debug) {
-                            Slog.v(TAG,
-                                    "calling onNotificationEnqueuedWithChannel " + sbnHolder);
+                        if (android.app.Flags.noSbnholder()) {
+                            assistant.onNotificationEnqueuedWithChannelFull(sbnToPost,
+                                    r.getChannel(), update);
+                        } else {
+                            final StatusBarNotificationHolder sbnHolder =
+                                    new StatusBarNotificationHolder(sbnToPost);
+
+                            assistant.onNotificationEnqueuedWithChannel(sbnHolder, r.getChannel(),
+                                    update);
                         }
-                        final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
-                        assistant.onNotificationEnqueuedWithChannel(sbnHolder, r.getChannel(),
-                                update);
                     } catch (RemoteException ex) {
                         Slog.e(TAG, "unable to notify assistant (enqueued): " + assistant, ex);
                     }
@@ -11966,7 +11969,7 @@
                     r.getSbn(),
                     r.getNotificationType(),
                     true /* sameUserOnly */,
-                    (assistant, sbnHolder) -> {
+                    (assistant, unused) -> {
                         try {
                             assistant.onNotificationVisibilityChanged(key, isVisible);
                         } catch (RemoteException ex) {
@@ -11986,7 +11989,7 @@
                     sbn,
                     notificationType,
                     true /* sameUserOnly */,
-                    (assistant, sbnHolder) -> {
+                    (assistant, unused) -> {
                         try {
                             assistant.onNotificationExpansionChanged(key, isUserAction, isExpanded);
                         } catch (RemoteException ex) {
@@ -12003,7 +12006,7 @@
                     r.getSbn(),
                     r.getNotificationType(),
                     true /* sameUserOnly */,
-                    (assistant, sbnHolder) -> {
+                    (assistant, unused) -> {
                         try {
                             assistant.onNotificationDirectReply(key);
                         } catch (RemoteException ex) {
@@ -12021,7 +12024,7 @@
                     sbn,
                     notificationType,
                     true /* sameUserOnly */,
-                    (assistant, sbnHolder) -> {
+                    (assistant, unused) -> {
                         try {
                             assistant.onSuggestedReplySent(
                                     key,
@@ -12044,7 +12047,7 @@
                     r.getSbn(),
                     r.getNotificationType(),
                     true /* sameUserOnly */,
-                    (assistant, sbnHolder) -> {
+                    (assistant, unused) -> {
                         try {
                             assistant.onActionClicked(
                                     key,
@@ -12069,10 +12072,17 @@
                     r.getSbn(),
                     r.getNotificationType(),
                     true /* sameUserOnly */,
-                    (assistant, sbnHolder) -> {
+                    (assistant, sbnToPost) -> {
                         try {
-                            assistant.onNotificationSnoozedUntilContext(
-                                    sbnHolder, snoozeCriterionId);
+                            if (android.app.Flags.noSbnholder()) {
+                                assistant.onNotificationSnoozedUntilContextFull(
+                                        sbnToPost, snoozeCriterionId);
+                            } else {
+                                final StatusBarNotificationHolder sbnHolder =
+                                        new StatusBarNotificationHolder(sbnToPost);
+                                assistant.onNotificationSnoozedUntilContext(
+                                        sbnHolder, snoozeCriterionId);
+                            }
                         } catch (RemoteException ex) {
                             Slog.e(TAG, "unable to notify assistant (snoozed): " + assistant, ex);
                         }
@@ -12086,7 +12096,7 @@
                     r.getSbn(),
                     r.getNotificationType(),
                     true /* sameUserOnly */,
-                    (assistant, sbnHolder) -> {
+                    (assistant, unused) -> {
                         try {
                             assistant.onNotificationClicked(key);
                         } catch (RemoteException ex) {
@@ -12129,7 +12139,7 @@
                 final StatusBarNotification sbn,
                 int notificationType,
                 boolean sameUserOnly,
-                BiConsumer<INotificationListener, StatusBarNotificationHolder> callback) {
+                BiConsumer<INotificationListener, StatusBarNotification> callback) {
             TrimCache trimCache = new TrimCache(sbn);
             // There should be only one, but it's a list, so while we enforce
             // singularity elsewhere, we keep it general here, to avoid surprises.
@@ -12151,9 +12161,7 @@
                 }
                 final INotificationListener assistant = (INotificationListener) info.service;
                 final StatusBarNotification sbnToPost = trimCache.ForListener(info);
-                final StatusBarNotificationHolder sbnHolder =
-                        new StatusBarNotificationHolder(sbnToPost);
-                mHandler.post(() -> callback.accept(assistant, sbnHolder));
+                mHandler.post(() -> callback.accept(assistant, sbnToPost));
             }
         }
 
@@ -13399,9 +13407,13 @@
         private void notifyPosted(final ManagedServiceInfo info,
                 final StatusBarNotification sbn, NotificationRankingUpdate rankingUpdate) {
             final INotificationListener listener = (INotificationListener) info.service;
-            StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
             try {
-                listener.onNotificationPosted(sbnHolder, rankingUpdate);
+                if (android.app.Flags.noSbnholder()) {
+                    listener.onNotificationPostedFull(sbn, rankingUpdate);
+                } else {
+                    StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
+                    listener.onNotificationPosted(sbnHolder, rankingUpdate);
+                }
             } catch (DeadObjectException ex) {
                 Slog.wtf(TAG, "unable to notify listener (posted): " + info, ex);
             } catch (RemoteException ex) {
@@ -13412,7 +13424,6 @@
         private void notifyRemoved(ManagedServiceInfo info, StatusBarNotification sbn,
                 NotificationRankingUpdate rankingUpdate, NotificationStats stats, int reason) {
             final INotificationListener listener = (INotificationListener) info.service;
-            StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
             try {
                 if (!CompatChanges.isChangeEnabled(NOTIFICATION_CANCELLATION_REASONS, info.uid)
                         && (reason == REASON_CHANNEL_REMOVED || reason == REASON_CLEAR_DATA)) {
@@ -13424,7 +13435,12 @@
                         && reason == REASON_ASSISTANT_CANCEL) {
                     reason = REASON_LISTENER_CANCEL;
                 }
-                listener.onNotificationRemoved(sbnHolder, rankingUpdate, stats, reason);
+                if (android.app.Flags.noSbnholder()) {
+                    listener.onNotificationRemovedFull(sbn, rankingUpdate, stats, reason);
+                } else {
+                    StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
+                    listener.onNotificationRemoved(sbnHolder, rankingUpdate, stats, reason);
+                }
             } catch (DeadObjectException ex) {
                 Slog.wtf(TAG, "unable to notify listener (removed): " + info, ex);
             } catch (RemoteException ex) {
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 76ea0b9..4690e02 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -880,7 +880,8 @@
                 PackageInstaller.STATUS_PENDING_USER_ACTION);
         broadcastIntent.putExtra(Intent.EXTRA_INTENT, dialogIntent);
         broadcastIntent.putExtra(Intent.EXTRA_USER, user);
-        sendIntent(statusReceiver, packageName, /* message= */ "", broadcastIntent);
+        mPm.mHandler.post(
+            () -> sendIntent(statusReceiver, packageName, /* message= */ "", broadcastIntent));
     }
 
     private void verifyUninstallPermissions() {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 5518bfa..1052c94 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -1022,7 +1022,7 @@
                 // Close.
                 file.finishWrite(outs);
             } catch (IOException e) {
-                Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
+                Slog.w(TAG, "Failed to write to file " + file.getBaseFile(), e);
                 file.failWrite(outs);
             }
         }
@@ -1055,7 +1055,7 @@
                     final String tag = parser.getName();
                     if (depth == 1) {
                         if (!TAG_ROOT.equals(tag)) {
-                            Slog.e(TAG, "Invalid root tag: " + tag);
+                            Slog.v(TAG, "Invalid root tag: " + tag);
                             return;
                         }
                         continue;
@@ -1066,7 +1066,7 @@
                             mRawLastResetTime.set(parseLongAttribute(parser, ATTR_VALUE));
                             break;
                         default:
-                            Slog.e(TAG, "Invalid tag: " + tag);
+                            Slog.v(TAG, "Invalid tag: " + tag);
                             break;
                     }
                 }
@@ -1113,7 +1113,7 @@
                 // Remove all dangling bitmap files.
                 cleanupDanglingBitmapDirectoriesLocked(userId);
             } catch (XmlPullParserException | IOException e) {
-                Slog.e(TAG, "Failed to write to file " + file, e);
+                Slog.w(TAG, "Failed to write to file " + file, e);
                 file.failWrite(os);
             }
         }
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/stats/Android.bp b/services/core/java/com/android/server/stats/Android.bp
index f7955e8..40923b6 100644
--- a/services/core/java/com/android/server/stats/Android.bp
+++ b/services/core/java/com/android/server/stats/Android.bp
@@ -11,3 +11,10 @@
     name: "stats_flags_lib",
     aconfig_declarations: "stats_flags",
 }
+
+java_aconfig_library {
+    name: "stats_flags_lib_host",
+    aconfig_declarations: "stats_flags",
+    host_supported: true,
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 54e4f8e..40ea931 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -61,6 +61,10 @@
 import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS__SOFTWARE_SHORTCUT_TYPE__UNKNOWN_TYPE;
 import static com.android.internal.util.FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER__OPPORTUNISTIC_DATA_SUB__NOT_OPPORTUNISTIC;
 import static com.android.internal.util.FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER__OPPORTUNISTIC_DATA_SUB__OPPORTUNISTIC;
+import static com.android.internal.util.FrameworkStatsLog.PRESSURE_STALL_INFORMATION__PSI_RESOURCE__PSI_RESOURCE_CPU;
+import static com.android.internal.util.FrameworkStatsLog.PRESSURE_STALL_INFORMATION__PSI_RESOURCE__PSI_RESOURCE_IO;
+import static com.android.internal.util.FrameworkStatsLog.PRESSURE_STALL_INFORMATION__PSI_RESOURCE__PSI_RESOURCE_MEMORY;
+import static com.android.internal.util.FrameworkStatsLog.PRESSURE_STALL_INFORMATION__PSI_RESOURCE__PSI_RESOURCE_UNKNOWN;
 import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__GEO;
 import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__MANUAL;
 import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__TELEPHONY;
@@ -68,6 +72,7 @@
 import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
 import static com.android.server.stats.Flags.accumulateNetworkStatsSinceBoot;
 import static com.android.server.stats.Flags.addMobileBytesTransferByProcStatePuller;
+import static com.android.server.stats.Flags.addPressureStallInformationPuller;
 import static com.android.server.stats.Flags.applyNetworkStatsPollRateLimit;
 import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs;
 import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs;
@@ -234,6 +239,8 @@
 import com.android.server.stats.pull.netstats.NetworkStatsAccumulator;
 import com.android.server.stats.pull.netstats.NetworkStatsExt;
 import com.android.server.stats.pull.netstats.SubInfo;
+import com.android.server.stats.pull.psi.PsiData;
+import com.android.server.stats.pull.psi.PsiExtractor;
 import com.android.server.storage.DiskStatsFileLogger;
 import com.android.server.storage.DiskStatsLoggingService;
 import com.android.server.timezonedetector.MetricsTimeZoneDetectorState;
@@ -459,6 +466,10 @@
     public static final boolean ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER =
                 addMobileBytesTransferByProcStatePuller();
 
+    // Whether or not to enable the new puller with pressure stall information.
+    public static final boolean ENABLE_PRESSURE_STALL_INFORMATION_PULLER =
+                addPressureStallInformationPuller();
+
     // Puller locks
     private final Object mDataBytesTransferLock = new Object();
     private final Object mBluetoothBytesTransferLock = new Object();
@@ -835,6 +846,8 @@
                         return pullHdrCapabilities(atomTag, data);
                     case FrameworkStatsLog.CACHED_APPS_HIGH_WATERMARK:
                         return pullCachedAppsHighWatermark(atomTag, data);
+                    case FrameworkStatsLog.PRESSURE_STALL_INFORMATION:
+                        return pullPressureStallInformation(atomTag, data);
                     default:
                         throw new UnsupportedOperationException("Unknown tagId=" + atomTag);
                 }
@@ -1045,6 +1058,9 @@
         registerPinnerServiceStats();
         registerHdrCapabilitiesPuller();
         registerCachedAppsHighWatermarkPuller();
+        if (ENABLE_PRESSURE_STALL_INFORMATION_PULLER) {
+            registerPressureStallInformation();
+        }
     }
 
     private void initMobileDataStatsPuller() {
@@ -5156,6 +5172,55 @@
         );
     }
 
+    private void registerPressureStallInformation() {
+        int tagId = FrameworkStatsLog.PRESSURE_STALL_INFORMATION;
+        mStatsManager.setPullAtomCallback(
+                tagId,
+                null,
+                DIRECT_EXECUTOR,
+                mStatsCallbackImpl
+        );
+    }
+
+    int pullPressureStallInformation(int atomTag, List<StatsEvent> pulledData) {
+        PsiExtractor psiExtractor = new PsiExtractor();
+        for (PsiData.ResourceType resourceType: PsiData.ResourceType.values()) {
+            PsiData psiData = psiExtractor.getPsiData(resourceType);
+            if (psiData == null) {
+                Slog.e(
+                        TAG,
+                        "Failed to pull PressureStallInformation atom for resource: "
+                                + resourceType.toString());
+                continue;
+            }
+            pulledData.add(FrameworkStatsLog.buildStatsEvent(
+                    atomTag,
+                    toProtoPsiResourceType(psiData.getResourceType()),
+                    psiData.getSomeAvg10SecPercentage(),
+                    psiData.getSomeAvg60SecPercentage(),
+                    psiData.getSomeAvg300SecPercentage(),
+                    psiData.getSomeTotalUsec(),
+                    psiData.getFullAvg10SecPercentage(),
+                    psiData.getFullAvg60SecPercentage(),
+                    psiData.getFullAvg300SecPercentage(),
+                    psiData.getFullTotalUsec()));
+        }
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    private int toProtoPsiResourceType(PsiData.ResourceType resourceType) {
+        if (resourceType == PsiData.ResourceType.CPU) {
+            return PRESSURE_STALL_INFORMATION__PSI_RESOURCE__PSI_RESOURCE_CPU;
+        } else if (resourceType == PsiData.ResourceType.MEMORY) {
+            return PRESSURE_STALL_INFORMATION__PSI_RESOURCE__PSI_RESOURCE_MEMORY;
+        } else if (resourceType == PsiData.ResourceType.IO) {
+            return PRESSURE_STALL_INFORMATION__PSI_RESOURCE__PSI_RESOURCE_IO;
+        } else {
+            return PRESSURE_STALL_INFORMATION__PSI_RESOURCE__PSI_RESOURCE_UNKNOWN;
+        }
+    }
+
+
     int pullSystemServerPinnerStats(int atomTag, List<StatsEvent> pulledData) {
         PinnerService pinnerService = LocalServices.getService(PinnerService.class);
         List<PinnedFileStats> pinnedFileStats = pinnerService.dumpDataForStatsd();
diff --git a/services/core/java/com/android/server/stats/stats_flags.aconfig b/services/core/java/com/android/server/stats/stats_flags.aconfig
index 8686458f..f5f3174 100644
--- a/services/core/java/com/android/server/stats/stats_flags.aconfig
+++ b/services/core/java/com/android/server/stats/stats_flags.aconfig
@@ -38,3 +38,11 @@
     bug: "352537247"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "add_pressure_stall_information_puller"
+    namespace: "statsd"
+    description: "Adds PressureStallInformation atom logging"
+    bug: "365731097"
+    is_fixed_read_only: true
+}
\ No newline at end of file
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/DeviceAdapter.java b/services/core/java/com/android/server/vibrator/DeviceAdapter.java
index 751e83c..370f212 100644
--- a/services/core/java/com/android/server/vibrator/DeviceAdapter.java
+++ b/services/core/java/com/android/server/vibrator/DeviceAdapter.java
@@ -55,8 +55,9 @@
 
     DeviceAdapter(VibrationSettings settings, SparseArray<VibratorController> vibrators) {
         mSegmentAdapters = Arrays.asList(
-                // TODO(b/167947076): add filter that removes unsupported primitives
                 // TODO(b/167947076): add filter that replaces unsupported prebaked with fallback
+                // Updates primitive delays to hardware supported pauses
+                new PrimitiveDelayAdapter(),
                 // Convert segments based on device capabilities
                 new RampToStepAdapter(settings.getRampStepDuration()),
                 new StepToRampAdapter(),
@@ -71,7 +72,9 @@
         );
         mSegmentsValidators = List.of(
                 // Validate Pwle segments base on the vibrators frequency range
-                new PwleSegmentsValidator()
+                new PwleSegmentsValidator(),
+                // Validate primitive segments based on device support
+                new PrimitiveSegmentsValidator()
         );
         mAvailableVibrators = vibrators;
         mAvailableVibratorIds = new int[vibrators.size()];
diff --git a/services/core/java/com/android/server/vibrator/PrimitiveDelayAdapter.java b/services/core/java/com/android/server/vibrator/PrimitiveDelayAdapter.java
new file mode 100644
index 0000000..d63fffd
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/PrimitiveDelayAdapter.java
@@ -0,0 +1,116 @@
+/*
+ * 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.vibrator;
+
+import static android.os.VibrationEffect.Composition.DELAY_TYPE_PAUSE;
+import static android.os.VibrationEffect.Composition.DELAY_TYPE_RELATIVE_START_OFFSET;
+
+import android.os.VibrationEffect.Composition.DelayType;
+import android.os.VibratorInfo;
+import android.os.vibrator.Flags;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.VibrationEffectSegment;
+
+import java.util.List;
+
+/**
+ * Adapter that converts between {@link DelayType} and the HAL supported pause delays.
+ *
+ * <p>Primitives that overlap due to the delays being shorter than the previous segments will be
+ * dropped from the effect here. Relative timings will still use the dropped primitives to preserve
+ * the design intention.
+ */
+final class PrimitiveDelayAdapter implements VibrationSegmentsAdapter {
+
+    PrimitiveDelayAdapter() {
+    }
+
+    @Override
+    public int adaptToVibrator(VibratorInfo info, List<VibrationEffectSegment> segments,
+            int repeatIndex) {
+        if (!Flags.primitiveCompositionAbsoluteDelay()) {
+            return repeatIndex;
+        }
+        int previousStartOffset = 0;
+        int segmentCount = segments.size();
+        for (int i = 0; i < segmentCount; i++) {
+            VibrationEffectSegment segment = segments.get(i);
+            if (i == repeatIndex) {
+                // Crossed the repeat line, reset start offset so repeating block is independent.
+                previousStartOffset = 0;
+            }
+
+            if (!(segment instanceof PrimitiveSegment primitive)
+                    || (primitive.getDelayType() == DELAY_TYPE_PAUSE)) {
+                // Effect will play normally, keep track of its start offset.
+                previousStartOffset = -calculateEffectDuration(info, segment);
+                continue;
+            }
+
+            int pause = calculatePause(primitive, previousStartOffset);
+            if (pause >= 0) {
+                segments.set(i, toPrimitiveWithPause(primitive, pause));
+                // Delay will be ignored from this calculation.
+                previousStartOffset = -calculateEffectDuration(info, primitive);
+            } else {
+                // Primitive overlapping with previous segment, ignore it.
+                segments.remove(i);
+                if (repeatIndex > i) {
+                    repeatIndex--;
+                }
+                segmentCount--;
+                i--;
+
+                // Keep the intended start time for future calculations. Here is an example:
+                // 10 20 30 40 50 60 70 | Timeline (D = relative delay, E = effect duration)
+                //  D  E  E  E  E       | D=10, E=40 | offset = 0   | pause = 10  | OK
+                //     D  E  E          | D=10, E=20 | offset = -40 | pause = -30 | IGNORED
+                //        D  E  E       | D=10, E=20 | offset = -30 | pause = -20 | IGNORED
+                //           D  E  E    | D=10, E=20 | offset = -20 | pause = -10 | IGNORED
+                //              D  E  E | D=10, E=20 | offset = -10 | pause = 0   | OK
+                previousStartOffset = pause;
+            }
+        }
+        return repeatIndex;
+    }
+
+    private static int calculatePause(PrimitiveSegment primitive, int previousStartOffset) {
+        if (primitive.getDelayType() == DELAY_TYPE_RELATIVE_START_OFFSET) {
+            return previousStartOffset + primitive.getDelay();
+        }
+        return primitive.getDelay();
+    }
+
+    private static int calculateEffectDuration(VibratorInfo info, VibrationEffectSegment segment) {
+        long segmentDuration = segment.getDuration(info);
+        if (segmentDuration < 0) {
+            // Duration unknown, default to zero.
+            return 0;
+        }
+        int effectDuration = (int) segmentDuration;
+        if (segment instanceof PrimitiveSegment primitive) {
+            // Ignore primitive delays from effect duration.
+            effectDuration -= primitive.getDelay();
+        }
+        return effectDuration;
+    }
+
+    private static PrimitiveSegment toPrimitiveWithPause(PrimitiveSegment primitive, int pause) {
+        return new PrimitiveSegment(primitive.getPrimitiveId(), primitive.getScale(),
+                pause, DELAY_TYPE_PAUSE);
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/PrimitiveSegmentsValidator.java b/services/core/java/com/android/server/vibrator/PrimitiveSegmentsValidator.java
new file mode 100644
index 0000000..a1567fc
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/PrimitiveSegmentsValidator.java
@@ -0,0 +1,59 @@
+/*
+ * 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.vibrator;
+
+import android.annotation.SuppressLint;
+import android.hardware.vibrator.IVibrator;
+import android.os.VibratorInfo;
+import android.os.vibrator.Flags;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.VibrationEffectSegment;
+
+import java.util.List;
+
+/**
+ * Validates primitive segments to ensure they are compatible with the device's capabilities.
+ *
+ * <p>The segments will be considered invalid if the device does not have
+ * {@link IVibrator#CAP_COMPOSE_EFFECTS} or if one of the primitives is not supported.
+ */
+final class PrimitiveSegmentsValidator implements VibrationSegmentsValidator {
+
+    @SuppressLint("WrongConstant") // using primitive id from validated segment
+    @Override
+    public boolean hasValidSegments(VibratorInfo info, List<VibrationEffectSegment> segments) {
+        int segmentCount = segments.size();
+        for (int i = 0; i < segmentCount; i++) {
+            if (!(segments.get(i) instanceof PrimitiveSegment primitive)) {
+                continue;
+            }
+            if (Flags.primitiveCompositionAbsoluteDelay()) {
+                // Primitive support checks introduced by this feature
+                if (!info.isPrimitiveSupported(primitive.getPrimitiveId())) {
+                    return false;
+                }
+            } else {
+                // Delay type support not available without this feature
+                if ((primitive.getDelayType() != PrimitiveSegment.DEFAULT_DELAY_TYPE)) {
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+}
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/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 1c11c67..c6e6e76 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2538,6 +2538,19 @@
                 }
             }
             if (!activityAllDrawn && !isActivityHome) {
+                // Only check the special case of a fragment host task because the starting window
+                // may not be visible if the client organizer delays the transition ready.
+                if (task.mTaskFragmentHostProcessName != null) {
+                    // It may be launched from a task trampoline that already has a starting window.
+                    // Return NONE because 2 consecutive splashes may not look smooth in visual.
+                    final Task prevTask = task.getParent().getTaskBelow(task);
+                    if (prevTask != null) {
+                        final ActivityRecord prevTaskTop = prevTask.getTopMostActivity();
+                        if (prevTaskTop != null && prevTaskTop.hasStartingWindow()) {
+                            return STARTING_WINDOW_TYPE_NONE;
+                        }
+                    }
+                }
                 return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
             }
         }
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index 9f40bed..25fdf89 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -17,8 +17,6 @@
 package com.android.server.wm;
 
 import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_DIMMER;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -44,7 +42,8 @@
      */
     private final WindowContainer<?> mHost;
 
-    private static final String TAG = TAG_WITH_CLASS_NAME ? "Dimmer" : TAG_WM;
+    private static final String TAG = "WindowManagerDimmer";
+
     DimState mDimState;
     final DimmerAnimationHelper.AnimationAdapterFactory mAnimationAdapterFactory;
 
@@ -69,9 +68,10 @@
 
         DimState() {
             mHostContainer = mHost;
-            mAnimationHelper = new DimmerAnimationHelper(mAnimationAdapterFactory);
+            mAnimationHelper = new DimmerAnimationHelper(mHost, mAnimationAdapterFactory);
             try {
                 mDimSurface = makeDimLayer();
+                EventLogTags.writeWmDimCreated(mHost.getName(), mDimSurface.getLayerId());
             } catch (Surface.OutOfResourcesException e) {
                 Log.w(TAG, "OutOfResourcesException creating dim surface");
             }
@@ -102,6 +102,11 @@
          * Prepare the dim for the exit animation
          */
         void exit(@NonNull SurfaceControl.Transaction t) {
+            EventLogTags.writeWmDimExit(mDimState.mDimSurface.getLayerId(),
+                    mDimState.mLastDimmingWindow != null
+                            ? mDimState.mLastDimmingWindow.getName() : "-",
+                    mDimState.mHostContainer.isVisible() ? 1 : 0,
+                    mAnimateExit ? 0 : 1);
             if (!mAnimateExit) {
                 remove(t);
             } else {
@@ -111,8 +116,10 @@
         }
 
         void remove(@NonNull SurfaceControl.Transaction t) {
+            EventLogTags.writeWmDimCancelAnim(mDimSurface.getLayerId(), "ready to remove");
             mAnimationHelper.stopCurrentAnimation(mDimSurface);
             if (mDimSurface.isValid()) {
+                EventLogTags.writeWmDimRemoved(mDimSurface.getLayerId());
                 t.remove(mDimSurface);
                 ProtoLog.d(WM_DEBUG_DIMMER,
                         "Removing dim surface %s on transaction %s", this, t);
@@ -126,6 +133,13 @@
             return "Dimmer#DimState with host=" + mHostContainer + ", surface=" + mDimSurface;
         }
 
+
+        String reasonForRemoving() {
+            return mLastDimmingWindow != null ? mLastDimmingWindow
+                    + " is dimming but host " + mHostContainer + " is not visibleRequested"
+                    : " no one is dimming";
+        }
+
         /**
          * Set the parameters to prepare the dim to be relative parented to the dimming container
          */
diff --git a/services/core/java/com/android/server/wm/DimmerAnimationHelper.java b/services/core/java/com/android/server/wm/DimmerAnimationHelper.java
index 0d0e548..1d447dd 100644
--- a/services/core/java/com/android/server/wm/DimmerAnimationHelper.java
+++ b/services/core/java/com/android/server/wm/DimmerAnimationHelper.java
@@ -76,9 +76,11 @@
             return mDimmingContainer != null && mDimmingContainer == other.mDimmingContainer;
         }
 
-        void inheritPropertiesFromAnimation(@NonNull AnimationSpec anim) {
-            mAlpha = anim.mCurrentAlpha;
-            mBlurRadius = anim.mCurrentBlur;
+        void inheritPropertiesFromAnimation(@Nullable AnimationSpec anim) {
+            if (anim != null) {
+                mAlpha = anim.mCurrentAlpha;
+                mBlurRadius = anim.mCurrentBlur;
+            }
         }
 
         @Override
@@ -92,11 +94,13 @@
     private final Change mRequestedProperties = new Change();
     private AnimationSpec mAlphaAnimationSpec;
 
+    private final SurfaceAnimationRunner mSurfaceAnimationRunner;
     private final AnimationAdapterFactory mAnimationAdapterFactory;
     private AnimationAdapter mLocalAnimationAdapter;
 
-    DimmerAnimationHelper(AnimationAdapterFactory animationFactory) {
+    DimmerAnimationHelper(WindowContainer<?> host, AnimationAdapterFactory animationFactory) {
         mAnimationAdapterFactory = animationFactory;
+        mSurfaceAnimationRunner = host.mWmService.mSurfaceAnimationRunner;
     }
 
     void setExitParameters() {
@@ -160,6 +164,7 @@
         }
 
         if (!startProperties.hasSameVisualProperties(mRequestedProperties)) {
+            EventLogTags.writeWmDimCancelAnim(dim.mDimSurface.getLayerId(), "new target values");
             stopCurrentAnimation(dim.mDimSurface);
 
             if (dim.mSkipAnimation
@@ -189,13 +194,15 @@
         ProtoLog.v(WM_DEBUG_DIMMER, "Starting animation on %s", dim);
         mAlphaAnimationSpec = getRequestedAnimationSpec(from, to);
         mLocalAnimationAdapter = mAnimationAdapterFactory.get(mAlphaAnimationSpec,
-                dim.mHostContainer.mWmService.mSurfaceAnimationRunner);
+                mSurfaceAnimationRunner);
 
         float targetAlpha = to.mAlpha;
+        EventLogTags.writeWmDimAnimate(dim.mDimSurface.getLayerId(), targetAlpha, to.mBlurRadius);
 
         mLocalAnimationAdapter.startAnimation(dim.mDimSurface, t,
                 ANIMATION_TYPE_DIMMER, /* finishCallback */ (type, animator) -> {
                     synchronized (dim.mHostContainer.mWmService.mGlobalLock) {
+                        EventLogTags.writeWmDimFinishAnim(dim.mDimSurface.getLayerId());
                         SurfaceControl.Transaction finishTransaction =
                                 dim.mHostContainer.getSyncTransaction();
                         setCurrentAlphaBlur(dim, finishTransaction);
@@ -208,18 +215,12 @@
                 });
     }
 
-    private boolean isAnimating() {
-        return mAlphaAnimationSpec != null;
-    }
-
     void stopCurrentAnimation(@NonNull SurfaceControl surface) {
-        if (mLocalAnimationAdapter != null && isAnimating()) {
-            // Save the current animation progress and cancel the animation
-            mCurrentProperties.inheritPropertiesFromAnimation(mAlphaAnimationSpec);
-            mLocalAnimationAdapter.onAnimationCancelled(surface);
-            mLocalAnimationAdapter = null;
-            mAlphaAnimationSpec = null;
-        }
+        // (If animating) save the current animation progress and cancel the animation
+        mCurrentProperties.inheritPropertiesFromAnimation(mAlphaAnimationSpec);
+        mSurfaceAnimationRunner.onAnimationCancelled(surface);
+        mLocalAnimationAdapter = null;
+        mAlphaAnimationSpec = null;
     }
 
     @NonNull
diff --git a/services/core/java/com/android/server/wm/EventLogTags.logtags b/services/core/java/com/android/server/wm/EventLogTags.logtags
index cc2249de..9d66886 100644
--- a/services/core/java/com/android/server/wm/EventLogTags.logtags
+++ b/services/core/java/com/android/server/wm/EventLogTags.logtags
@@ -87,3 +87,16 @@
 
 # Entering pip called
 38000 wm_enter_pip (User|1|5),(Token|1|5),(Component Name|3),(is Auto Enter|3)
+
+# Dim layer is created
+38200 wm_dim_created (Host|3),(Surface|1)
+# Dimmer is ready for removal
+38201 wm_dim_exit (Surface|1),(dimmingWindow|3),(hostIsVisible|1),(removeImmediately|1)
+# Dimmer is starting an animation
+38202 wm_dim_animate (Surface|1, (toAlpha|5), (toBlur|5))
+# Dimmer animation is cancelled
+38203 wm_dim_cancel_anim (Surface|1),(reason|3)
+# Dimmer animation is finished
+38204 wm_dim_finish_anim (Surface|1)
+# Dimmer removing surface
+38205 wm_dim_removed (Surface|1)
\ No newline at end of file
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/Session.java b/services/core/java/com/android/server/wm/Session.java
index 1bb4c41..0f66b93 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -706,6 +706,10 @@
                 win.setRequestedVisibleTypes(requestedVisibleTypes);
                 win.getDisplayContent().getInsetsPolicy().onRequestedVisibleTypesChanged(win,
                         imeStatsToken);
+                final Task task = win.getTask();
+                if (task != null) {
+                    task.dispatchTaskInfoChangedIfNeeded(/* forced= */ true);
+                }
             } else {
                 EmbeddedWindowController.EmbeddedWindow embeddedWindow = null;
                 if (android.view.inputmethod.Flags.refactorInsetsController()) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 352dc52..dbc3b76c2 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3436,7 +3436,8 @@
         info.isSleeping = shouldSleepActivities();
         info.isTopActivityTransparent = top != null && !top.fillsParent();
         info.lastNonFullscreenBounds = topTask.mLastNonFullscreenBounds;
-        final WindowState windowState = top != null ? top.findMainWindow() : null;
+        final WindowState windowState = top != null
+                ? top.findMainWindow(/* includeStartingApp= */ false) : null;
         info.requestedVisibleTypes = (windowState != null && Flags.enableFullyImmersiveInDesktop())
                 ? windowState.getRequestedVisibleTypes() : WindowInsets.Type.defaultVisible();
         AppCompatUtils.fillAppCompatTaskInfo(this, info, top);
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/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 079170a..81af78e 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2989,15 +2989,7 @@
         return (mAttrs.flags & FLAG_SHOW_WHEN_LOCKED) != 0;
     }
 
-    @Override
-    void resolveOverrideConfiguration(Configuration newParentConfig) {
-        super.resolveOverrideConfiguration(newParentConfig);
-        if (mActivityRecord != null) {
-            // Let the activity decide whether to apply the size override.
-            return;
-        }
-        final Configuration resolvedConfig = getResolvedOverrideConfiguration();
-        resolvedConfig.seq = newParentConfig.seq;
+    void applySizeOverride(Configuration newParentConfig, Configuration resolvedConfig) {
         applySizeOverrideIfNeeded(
                 getDisplayContent(),
                 mSession.mProcess.mInfo,
@@ -3380,8 +3372,10 @@
             if (cleanupOnResume) {
                 requestUpdateWallpaperIfNeeded();
             }
-            mDestroying = false;
-            destroyedSomething = true;
+            if (!mHasSurface) {
+                mDestroying = false;
+                destroyedSomething = true;
+            }
 
             // Since mDestroying will affect ActivityRecord#allDrawn, we need to perform another
             // traversal in case we are waiting on this window to start the transition.
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 44e237a..004f406 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -672,6 +672,15 @@
             getResolvedOverrideConfiguration().updateFrom(
                     mFixedRotationTransformState.mRotatedOverrideConfiguration);
         }
+        if (asActivityRecord() == null) {
+            // Let ActivityRecord override the config if there is one. Otherwise, override here.
+            // Resolve WindowToken's configuration by the latest window.
+            final WindowState win = getTopChild();
+            if (win != null) {
+                final Configuration resolvedConfig = getResolvedOverrideConfiguration();
+                win.applySizeOverride(newParentConfig, resolvedConfig);
+            }
+        }
     }
 
     @Override
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 d221e8c..dff3438 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -9004,26 +9004,13 @@
         if (!mHasFeature) {
             return;
         }
-
-        CallerIdentity caller;
-        if (Flags.setAutoTimeEnabledCoexistence()) {
-            caller = getCallerIdentity(who, callerPackageName);
-        } else {
-            caller = getCallerIdentity(who);
-        }
-
-        if (Flags.setAutoTimeEnabledCoexistence()) {
-            // The effect of this policy is device-wide.
-            enforcePermission(SET_TIME, caller.getPackageName(), UserHandle.USER_ALL);
-        } else {
-            Objects.requireNonNull(who, "ComponentName is null");
-            Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
-                    || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(
-                    caller));
-        }
+        CallerIdentity caller = getCallerIdentity(who);
+        Objects.requireNonNull(who, "ComponentName is null");
+        Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
+                || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(
+                caller));
         mInjector.binderWithCleanCallingIdentity(() ->
                 mInjector.settingsGlobalPutInt(Settings.Global.AUTO_TIME, enabled ? 1 : 0));
-
         DevicePolicyEventLogger
                 .createEvent(DevicePolicyEnums.SET_AUTO_TIME)
                 .setAdmin(caller.getPackageName())
@@ -9039,26 +9026,77 @@
         if (!mHasFeature) {
             return false;
         }
-        CallerIdentity caller;
-        if (Flags.setAutoTimeEnabledCoexistence()) {
-            caller = getCallerIdentity(who, callerPackageName);
-        } else {
-            caller = getCallerIdentity(who);
-        }
+        CallerIdentity caller = getCallerIdentity(who);
 
-        if (Flags.setAutoTimeEnabledCoexistence()) {
-            enforceCanQuery(SET_TIME, caller.getPackageName(), UserHandle.USER_ALL);
-        } else {
-            Objects.requireNonNull(who, "ComponentName is null");
-            Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
-                    || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(
-                    caller));
-        }
+        Objects.requireNonNull(who, "ComponentName is null");
+        Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
+                || isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller));
 
         return mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) > 0;
     }
 
     /**
+     * Set whether auto time is enabled on the device.
+     */
+    @Override
+    public void setAutoTimePolicy(String callerPackageName, int policy) {
+        if (!mHasFeature) {
+            return;
+        }
+
+        final Set<Integer> allowedValues =
+                Set.of(
+                        DevicePolicyManager.AUTO_TIME_ENABLED,
+                        DevicePolicyManager.AUTO_TIME_DISABLED,
+                        DevicePolicyManager.AUTO_TIME_NOT_CONTROLLED_BY_POLICY);
+        Preconditions.checkArgument(
+                allowedValues.contains(policy), "Provided mode is not one of the allowed values.");
+
+        CallerIdentity caller = getCallerIdentity(callerPackageName);
+        // The effect of this policy is device-wide.
+        EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
+                /* who */ null,
+                SET_TIME,
+                caller.getPackageName(),
+                UserHandle.USER_ALL
+        );
+        if (policy == DevicePolicyManager.AUTO_TIME_NOT_CONTROLLED_BY_POLICY) {
+            mDevicePolicyEngine.removeGlobalPolicy(PolicyDefinition.AUTO_TIME, enforcingAdmin);
+        } else {
+            mDevicePolicyEngine.setGlobalPolicy(
+                    PolicyDefinition.AUTO_TIME,
+                    enforcingAdmin,
+                    new IntegerPolicyValue(policy));
+            DevicePolicyEventLogger
+                    .createEvent(DevicePolicyEnums.SET_AUTO_TIME)
+                    .setAdmin(caller.getPackageName())
+                    .setBoolean(policy == DevicePolicyManager.AUTO_TIME_ENABLED)
+                    .write();
+        }
+    }
+
+    /**
+     * Returns whether auto time is used on the device or not.
+     */
+    @Override
+    public int getAutoTimePolicy(String callerPackageName) {
+        if (!mHasFeature) {
+            return DevicePolicyManager.AUTO_TIME_NOT_CONTROLLED_BY_POLICY;
+        }
+        CallerIdentity caller = getCallerIdentity(callerPackageName);
+        // The effect of this policy is device-wide.
+        EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
+                /* who */ null,
+                SET_TIME,
+                caller.getPackageName(),
+                UserHandle.USER_ALL
+        );
+        Integer state = mDevicePolicyEngine.getGlobalPolicySetByAdmin(
+                PolicyDefinition.AUTO_TIME, enforcingAdmin);
+        return state != null ? state : DevicePolicyManager.AUTO_TIME_NOT_CONTROLLED_BY_POLICY;
+    }
+
+    /**
      * Set whether auto time zone is enabled on the device.
      */
     @Override
@@ -13324,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());
@@ -13460,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 =
@@ -13477,6 +13543,7 @@
                     userId);
         }
     }
+
     private void setGlobalUserRestrictionInternal(
             EnforcingAdmin admin, String key, boolean enabled) {
         PolicyDefinition<Boolean> policyDefinition =
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/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index a5aeaac..24b16b7 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -349,6 +349,16 @@
                     PolicyEnforcerCallbacks::setMtePolicy,
                     new IntegerPolicySerializer());
 
+    static PolicyDefinition<Integer> AUTO_TIME = new PolicyDefinition<>(
+            new NoArgsPolicyKey(DevicePolicyIdentifiers.AUTO_TIME_POLICY),
+            new TopPriority<>(List.of(
+                    EnforcingAdmin.getRoleAuthorityOf(SYSTEM_SUPERVISION_ROLE),
+                    EnforcingAdmin.getRoleAuthorityOf(DEVICE_LOCK_CONTROLLER_ROLE),
+                    EnforcingAdmin.DPC_AUTHORITY)),
+            POLICY_FLAG_GLOBAL_ONLY_POLICY,
+            PolicyEnforcerCallbacks::setAutoTimePolicy,
+            new IntegerPolicySerializer());
+
     private static final Map<String, PolicyDefinition<?>> POLICY_DEFINITIONS = new HashMap<>();
     private static Map<String, Integer> USER_RESTRICTION_FLAGS = new HashMap<>();
 
@@ -397,6 +407,7 @@
                 PACKAGES_SUSPENDED);
         POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.MEMORY_TAGGING_POLICY,
                 MEMORY_TAGGING);
+        POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.AUTO_TIME_POLICY, AUTO_TIME);
 
         // User Restriction Policies
         USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_MODIFY_ACCOUNTS, /* flags= */ 0);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index 40d8dae..8f80004 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -212,6 +212,25 @@
         return AndroidFuture.completedFuture(true);
     }
 
+    public static CompletableFuture<Boolean> setAutoTimePolicy(
+            Integer policy, Context context, Integer userId, PolicyKey policyKey) {
+        if (!Flags.setAutoTimeEnabledCoexistence()) {
+            Slogf.w(LOG_TAG, "Trying to enforce setAutoTimePolicy while flag is off.");
+            return AndroidFuture.completedFuture(true);
+        }
+        return Binder.withCleanCallingIdentity(() -> {
+            Objects.requireNonNull(context);
+            if (policy != null
+                    && policy == DevicePolicyManager.AUTO_TIME_NOT_CONTROLLED_BY_POLICY) {
+                return AndroidFuture.completedFuture(false);
+            }
+            int enabled = policy != null && policy == DevicePolicyManager.AUTO_TIME_ENABLED ? 1 : 0;
+            return AndroidFuture.completedFuture(
+                    Settings.Global.putInt(
+                            context.getContentResolver(), Settings.Global.AUTO_TIME,  enabled));
+        });
+    }
+
     private static class BlockingCallback {
         private final CountDownLatch mLatch = new CountDownLatch(1);
         private final AtomicReference<Boolean> mValue = new AtomicReference<>();
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index 69714f3..3fdb53f 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -582,6 +582,34 @@
             PackageImpl::setSystemExt,
             true
         ),
+        getSetByValue(
+            AndroidPackage::getAlternateLauncherIconResIds,
+            PackageImpl::setAlternateLauncherIconResIds,
+            intArrayOf(3, 5, 7),
+            compare = { first, second ->
+                equalBy(
+                    first, second,
+                    { it.size },
+                    { it[0] },
+                    { it[1] },
+                    { it[2] }
+                )
+            }
+        ),
+        getSetByValue(
+            AndroidPackage::getAlternateLauncherLabelResIds,
+            PackageImpl::setAlternateLauncherLabelResIds,
+            intArrayOf(3, 5, 7),
+            compare = { first, second ->
+                equalBy(
+                    first, second,
+                    { it.size },
+                    { it[0] },
+                    { it[1] },
+                    { it[2] }
+                )
+            }
+        ),
     )
 
     override fun initialObject() = PackageImpl.forParsing(
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/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java
index 88ed615..81026fd 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/DeviceAdapterTest.java
@@ -16,6 +16,16 @@
 
 package com.android.server.vibrator;
 
+import static android.os.VibrationEffect.Composition.DELAY_TYPE_PAUSE;
+import static android.os.VibrationEffect.Composition.DELAY_TYPE_RELATIVE_START_OFFSET;
+import static android.os.VibrationEffect.Composition.PRIMITIVE_CLICK;
+import static android.os.VibrationEffect.Composition.PRIMITIVE_QUICK_FALL;
+import static android.os.VibrationEffect.Composition.PRIMITIVE_QUICK_RISE;
+import static android.os.VibrationEffect.Composition.PRIMITIVE_SLOW_RISE;
+import static android.os.VibrationEffect.Composition.PRIMITIVE_SPIN;
+import static android.os.VibrationEffect.Composition.PRIMITIVE_THUD;
+import static android.os.VibrationEffect.Composition.PRIMITIVE_TICK;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.when;
@@ -38,7 +48,7 @@
 import android.os.vibrator.VibrationConfig;
 import android.os.vibrator.VibrationEffectSegment;
 import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.SparseArray;
 
@@ -60,6 +70,7 @@
     private static final int PWLE_VIBRATOR_ID = 2;
     private static final int PWLE_WITHOUT_FREQUENCIES_VIBRATOR_ID = 3;
     private static final int PWLE_V2_VIBRATOR_ID = 4;
+    private static final int BASIC_VIBRATOR_ID = 5;
     private static final float TEST_MIN_FREQUENCY = 50;
     private static final float TEST_RESONANT_FREQUENCY = 150;
     private static final float TEST_FREQUENCY_RESOLUTION = 25;
@@ -73,6 +84,7 @@
     private static final float PWLE_V2_MIN_FREQUENCY = TEST_FREQUENCIES_HZ[0];
     private static final float PWLE_V2_MAX_FREQUENCY =
             TEST_FREQUENCIES_HZ[TEST_FREQUENCIES_HZ.length - 1];
+    private static final int TEST_PRIMITIVE_DURATION = 20;
 
     @Rule
     public MockitoRule mMockitoRule = MockitoJUnit.rule();
@@ -104,6 +116,7 @@
         vibrators.put(PWLE_WITHOUT_FREQUENCIES_VIBRATOR_ID,
                 createPwleWithoutFrequenciesVibratorController(
                         PWLE_WITHOUT_FREQUENCIES_VIBRATOR_ID));
+        vibrators.put(BASIC_VIBRATOR_ID, createBasicVibratorController(BASIC_VIBRATOR_ID));
         mAdapter = new DeviceAdapter(mVibrationSettings, vibrators);
     }
 
@@ -118,12 +131,12 @@
                 new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_SPIN, 0.5f, 100)),
                 /* repeatIndex= */ -1);
 
-        assertThat(mAdapter.adaptToVibrator(EMPTY_VIBRATOR_ID, effect)).isEqualTo(effect);
+        assertThat(mAdapter.adaptToVibrator(BASIC_VIBRATOR_ID, effect)).isEqualTo(effect);
         assertThat(mAdapter.adaptToVibrator(PWLE_VIBRATOR_ID, effect)).isEqualTo(effect);
     }
 
     @Test
-    @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
     public void testVendorEffect_returnsOriginalSegment() {
         PersistableBundle vendorData = new PersistableBundle();
         vendorData.putInt("key", 1);
@@ -236,10 +249,10 @@
         VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
                 new PrebakedSegment(
                         VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_LIGHT),
-                new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 10),
+                new StepSegment(1, 0, 10),
                 new PrebakedSegment(
                         VibrationEffect.EFFECT_THUD, true, VibrationEffect.EFFECT_STRENGTH_STRONG),
-                new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_SPIN, 0.5f, 100)),
+                new StepSegment(1, 0, 10)),
                 /* repeatIndex= */ -1);
 
         CombinedVibration expected = CombinedVibration.createParallel(effect);
@@ -262,6 +275,11 @@
                         new StepSegment(1, 175, 10),
                         new StepSegment(1, 0, 50)),
                         /* repeatIndex= */ 1))
+                .addVibrator(BASIC_VIBRATOR_ID, new VibrationEffect.Composed(Arrays.asList(
+                        // Step(amplitude, frequencyHz, duration)
+                        new StepSegment(1, 175, 10),
+                        new StepSegment(1, 0, 50)),
+                        /* repeatIndex= */ 1))
                 .addVibrator(PWLE_VIBRATOR_ID, new VibrationEffect.Composed(Arrays.asList(
                 // Ramp(startAmplitude, endAmplitude, startFrequencyHz, endFrequencyHz, duration)
                         new RampSegment(0.72f, 0.72f, 175, 175, 10),
@@ -308,7 +326,7 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
     public void testPwleSegment_withoutPwleV2Capability_returnsNull() {
         VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
                 new PrimitiveSegment(VibrationEffect.Composition.PRIMITIVE_SPIN, 0.5f, 100),
@@ -318,12 +336,12 @@
                 /* repeatIndex= */ 1);
 
         VibrationEffect.Composed adaptedEffect =
-                (VibrationEffect.Composed) mAdapter.adaptToVibrator(EMPTY_VIBRATOR_ID, effect);
+                (VibrationEffect.Composed) mAdapter.adaptToVibrator(BASIC_VIBRATOR_ID, effect);
         assertThat(adaptedEffect).isNull();
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
     public void testPwleSegment_withPwleV2Capability_returnsAdaptedSegments() {
         VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
                 new PwleSegment(1, 0.2f, 30, 60, 20),
@@ -345,7 +363,7 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
     public void testPwleSegment_withFrequenciesBelowSupportedRange_returnsNull() {
         float frequencyBelowSupportedRange = PWLE_V2_MIN_FREQUENCY - 1f;
         VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
@@ -362,7 +380,7 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
     public void testPwleSegment_withFrequenciesAboveSupportedRange_returnsNull() {
         float frequencyAboveSupportedRange = PWLE_V2_MAX_FREQUENCY + 1f;
         VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
@@ -378,22 +396,79 @@
         assertThat(adapter.adaptToVibrator(PWLE_V2_VIBRATOR_ID, effect)).isNull();
     }
 
+    @Test
+    @DisableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
+    public void testPrimitiveWithRelativeDelay_withoutFlag_returnsNull() {
+        VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
+                new PrimitiveSegment(PRIMITIVE_TICK, 1, 10, DELAY_TYPE_RELATIVE_START_OFFSET),
+                new PrimitiveSegment(PRIMITIVE_TICK, 0.5f, 10, DELAY_TYPE_RELATIVE_START_OFFSET),
+                new PrimitiveSegment(PRIMITIVE_CLICK, 1, 100, DELAY_TYPE_RELATIVE_START_OFFSET)),
+                /* repeatIndex= */ -1);
+
+        assertThat(mAdapter.adaptToVibrator(EMPTY_VIBRATOR_ID, effect)).isNull();
+        assertThat(mAdapter.adaptToVibrator(BASIC_VIBRATOR_ID, effect)).isNull();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
+    public void testUnsupportedPrimitives_withFlag_returnsNull() {
+        VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
+                new PrimitiveSegment(PRIMITIVE_TICK, 1, 10),
+                new PrimitiveSegment(PRIMITIVE_TICK, 0.5f, 10),
+                new PrimitiveSegment(PRIMITIVE_CLICK, 1, 100)),
+                /* repeatIndex= */ -1);
+
+        assertThat(mAdapter.adaptToVibrator(EMPTY_VIBRATOR_ID, effect)).isNull();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
+    public void testPrimitiveWithRelativeDelay_returnsPrimitiveWithPauseDelays() {
+        int expectedPause = 50;
+        int relativeDelay = 50 + TEST_PRIMITIVE_DURATION - 1;
+        VibrationEffect.Composed effect = new VibrationEffect.Composed(Arrays.asList(
+                // Originally requested (overlapping):
+                // tick @ 10ms / tick @ 11ms / click @ 69ms + 20ms pause + click
+                // Actually played:
+                // 10ms pause + tick + 50ms pause + click + 20ms pause + click
+                new PrimitiveSegment(PRIMITIVE_TICK, 1, 10, DELAY_TYPE_RELATIVE_START_OFFSET),
+                new PrimitiveSegment(PRIMITIVE_TICK, 0.5f, 1, DELAY_TYPE_RELATIVE_START_OFFSET),
+                new PrimitiveSegment(PRIMITIVE_CLICK, 1, relativeDelay,
+                        DELAY_TYPE_RELATIVE_START_OFFSET),
+                new PrimitiveSegment(PRIMITIVE_CLICK, 0.5f, 20, DELAY_TYPE_PAUSE)),
+                /* repeatIndex= */ -1);
+
+        // Delay based on primitive duration
+        VibrationEffect.Composed expected = new VibrationEffect.Composed(Arrays.asList(
+                new PrimitiveSegment(PRIMITIVE_TICK, 1, 10, DELAY_TYPE_PAUSE),
+                new PrimitiveSegment(PRIMITIVE_CLICK, 1, expectedPause, DELAY_TYPE_PAUSE),
+                new PrimitiveSegment(PRIMITIVE_CLICK, 0.5f, 20, DELAY_TYPE_PAUSE)),
+                /* repeatIndex= */ -1);
+
+        assertThat(mAdapter.adaptToVibrator(EMPTY_VIBRATOR_ID, effect)).isNull();
+        assertThat(mAdapter.adaptToVibrator(BASIC_VIBRATOR_ID, effect)).isEqualTo(expected);
+    }
+
     private VibratorController createEmptyVibratorController(int vibratorId) {
         return new FakeVibratorControllerProvider(mTestLooper.getLooper())
                 .newVibratorController(vibratorId, (id, vibrationId)  -> {});
     }
 
+    private VibratorController createBasicVibratorController(int vibratorId) {
+        FakeVibratorControllerProvider provider = createVibratorProviderWithEffects(
+                IVibrator.CAP_COMPOSE_EFFECTS);
+        return provider.newVibratorController(vibratorId, (id, vibrationId)  -> {});
+    }
+
     private VibratorController createPwleWithoutFrequenciesVibratorController(int vibratorId) {
-        FakeVibratorControllerProvider provider = new FakeVibratorControllerProvider(
-                mTestLooper.getLooper());
-        provider.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+        FakeVibratorControllerProvider provider = createVibratorProviderWithEffects(
+                IVibrator.CAP_COMPOSE_EFFECTS, IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
         return provider.newVibratorController(vibratorId, (id, vibrationId)  -> {});
     }
 
     private VibratorController createPwleVibratorController(int vibratorId) {
-        FakeVibratorControllerProvider provider = new FakeVibratorControllerProvider(
-                mTestLooper.getLooper());
-        provider.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
+        FakeVibratorControllerProvider provider = createVibratorProviderWithEffects(
+                IVibrator.CAP_COMPOSE_EFFECTS, IVibrator.CAP_COMPOSE_PWLE_EFFECTS);
         provider.setResonantFrequency(TEST_RESONANT_FREQUENCY);
         provider.setMinFrequency(TEST_MIN_FREQUENCY);
         provider.setFrequencyResolution(TEST_FREQUENCY_RESOLUTION);
@@ -402,9 +477,8 @@
     }
 
     private VibratorController createPwleV2VibratorController(int vibratorId) {
-        FakeVibratorControllerProvider provider = new FakeVibratorControllerProvider(
-                mTestLooper.getLooper());
-        provider.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2);
+        FakeVibratorControllerProvider provider = createVibratorProviderWithEffects(
+                IVibrator.CAP_COMPOSE_EFFECTS, IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2);
         provider.setResonantFrequency(TEST_RESONANT_FREQUENCY);
         provider.setFrequenciesHz(TEST_FREQUENCIES_HZ);
         provider.setOutputAccelerationsGs(TEST_OUTPUT_ACCELERATIONS_GS);
@@ -414,4 +488,15 @@
 
         return provider.newVibratorController(vibratorId, (id, vibrationId)  -> {});
     }
+
+    private FakeVibratorControllerProvider createVibratorProviderWithEffects(int... capabilities) {
+        FakeVibratorControllerProvider provider = new FakeVibratorControllerProvider(
+                mTestLooper.getLooper());
+        provider.setCapabilities(capabilities);
+        provider.setSupportedPrimitives(PRIMITIVE_CLICK, PRIMITIVE_TICK, PRIMITIVE_THUD,
+                PRIMITIVE_SPIN, PRIMITIVE_QUICK_RISE, PRIMITIVE_QUICK_FALL, PRIMITIVE_SLOW_RISE);
+        provider.setSupportedEffects(VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_TICK);
+        provider.setPrimitiveDuration(TEST_PRIMITIVE_DURATION);
+        return provider;
+    }
 }
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/PrimitiveDelayAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/PrimitiveDelayAdapterTest.java
new file mode 100644
index 0000000..f4a6f82
--- /dev/null
+++ b/services/tests/vibrator/src/com/android/server/vibrator/PrimitiveDelayAdapterTest.java
@@ -0,0 +1,195 @@
+/*
+ * 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.vibrator;
+
+import static android.os.VibrationEffect.Composition.DELAY_TYPE_PAUSE;
+import static android.os.VibrationEffect.Composition.DELAY_TYPE_RELATIVE_START_OFFSET;
+import static android.os.VibrationEffect.Composition.PRIMITIVE_CLICK;
+import static android.os.VibrationEffect.Composition.PRIMITIVE_TICK;
+
+import static org.junit.Assert.assertEquals;
+
+import android.hardware.vibrator.IVibrator;
+import android.os.VibrationEffect;
+import android.os.VibratorInfo;
+import android.os.vibrator.Flags;
+import android.os.vibrator.PrebakedSegment;
+import android.os.vibrator.PrimitiveSegment;
+import android.os.vibrator.RampSegment;
+import android.os.vibrator.StepSegment;
+import android.os.vibrator.VibrationEffectSegment;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class PrimitiveDelayAdapterTest {
+    private static final VibratorInfo EMPTY_VIBRATOR_INFO = new VibratorInfo.Builder(0).build();
+    private static final VibratorInfo BASIC_VIBRATOR_INFO = createVibratorInfoWithPrimitives(
+            new int[] { PRIMITIVE_CLICK, PRIMITIVE_TICK },
+            new int[] { 20, 10 });
+
+    private PrimitiveDelayAdapter mAdapter;
+
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+    @Before
+    public void setUp() throws Exception {
+        mAdapter = new PrimitiveDelayAdapter();
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
+    public void testPrimitiveSegments_flagDisabled_keepsListUnchanged() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new PrimitiveSegment(PRIMITIVE_CLICK, 1f, 100, DELAY_TYPE_RELATIVE_START_OFFSET),
+                new PrimitiveSegment(PRIMITIVE_TICK, 0.5f, 10, DELAY_TYPE_PAUSE)));
+        List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
+
+        assertEquals(-1, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, -1));
+        assertEquals(1, mAdapter.adaptToVibrator(BASIC_VIBRATOR_INFO, segments, 1));
+
+        assertEquals(originalSegments, segments);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
+    public void testNonPrimitiveSegments_keepsListUnchanged() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 1, /* duration= */ 10),
+                new RampSegment(/* startAmplitude= */ 0.8f, /* endAmplitude= */ 0.2f,
+                        /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 1, /* duration= */ 20),
+                new PrebakedSegment(VibrationEffect.EFFECT_CLICK, false,
+                        VibrationEffect.EFFECT_STRENGTH_LIGHT)));
+        List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
+
+        assertEquals(-1, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, -1));
+        assertEquals(1, mAdapter.adaptToVibrator(BASIC_VIBRATOR_INFO, segments, 1));
+
+        assertEquals(originalSegments, segments);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
+    public void testPrimitiveWithPause_keepsListUnchanged() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new PrimitiveSegment(PRIMITIVE_CLICK, 1f, 100, DELAY_TYPE_PAUSE),
+                new PrimitiveSegment(PRIMITIVE_TICK, 0.5f, 10, DELAY_TYPE_PAUSE)));
+        List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
+
+        assertEquals(-1, mAdapter.adaptToVibrator(EMPTY_VIBRATOR_INFO, segments, -1));
+        assertEquals(1, mAdapter.adaptToVibrator(BASIC_VIBRATOR_INFO, segments, 1));
+
+        assertEquals(originalSegments, segments);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
+    public void testPrimitiveWithRelativeDelay_afterPrimitive_usesPrimitiveStartTimeForDelay() {
+        VibratorInfo info = createVibratorInfoWithPrimitives(
+                new int[] { PRIMITIVE_CLICK }, new int[] { 20 });
+
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new PrimitiveSegment(PRIMITIVE_CLICK, 0.1f, 100, DELAY_TYPE_RELATIVE_START_OFFSET),
+                new PrimitiveSegment(PRIMITIVE_CLICK, 0.2f, 10, DELAY_TYPE_RELATIVE_START_OFFSET),
+                new PrimitiveSegment(PRIMITIVE_CLICK, 0.3f, 0, DELAY_TYPE_RELATIVE_START_OFFSET),
+                new PrimitiveSegment(PRIMITIVE_CLICK, 0.4f, 10, DELAY_TYPE_RELATIVE_START_OFFSET)));
+
+        List<VibrationEffectSegment> expectedSegments = new ArrayList<>(Arrays.asList(
+                new PrimitiveSegment(PRIMITIVE_CLICK, 0.1f, 100, DELAY_TYPE_PAUSE),
+                new PrimitiveSegment(PRIMITIVE_CLICK, 0.4f, 0, DELAY_TYPE_PAUSE)));
+
+        // Repeat index is fixed after removals
+        assertEquals(-1, mAdapter.adaptToVibrator(info, segments, -1));
+
+        assertEquals(expectedSegments, segments);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
+    public void testPrimitiveWithRelativeDelay_afterRepeatIndex_usesPauseAsFirstDelay() {
+        VibratorInfo info = createVibratorInfoWithPrimitives(
+                new int[] { PRIMITIVE_CLICK }, new int[] { 20 });
+
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new PrimitiveSegment(PRIMITIVE_CLICK, 0.1f, 100, DELAY_TYPE_RELATIVE_START_OFFSET),
+                new PrimitiveSegment(PRIMITIVE_CLICK, 0.2f, 10, DELAY_TYPE_RELATIVE_START_OFFSET),
+                new PrimitiveSegment(PRIMITIVE_CLICK, 0.3f, 10, DELAY_TYPE_RELATIVE_START_OFFSET),
+                new PrimitiveSegment(PRIMITIVE_CLICK, 0.4f, 10, DELAY_TYPE_RELATIVE_START_OFFSET)));
+
+        List<VibrationEffectSegment> expectedSegments = new ArrayList<>(Arrays.asList(
+                new PrimitiveSegment(PRIMITIVE_CLICK, 0.1f, 100, DELAY_TYPE_PAUSE),
+                new PrimitiveSegment(PRIMITIVE_CLICK, 0.3f, 10, DELAY_TYPE_PAUSE)));
+
+        // Relative offset reset after repeat index.
+        assertEquals(1, mAdapter.adaptToVibrator(info, segments, 2));
+
+        assertEquals(expectedSegments, segments);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
+    public void testPrimitiveWithRelativeDelayAfter_afterStep_usesSegmentStartTimeForDelay() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 1, /* duration= */ 10),
+                new PrimitiveSegment(PRIMITIVE_CLICK, 1f, 10, DELAY_TYPE_RELATIVE_START_OFFSET)));
+
+        List<VibrationEffectSegment> expectedSegments = new ArrayList<>(Arrays.asList(
+                new StepSegment(/* amplitude= */ 0, /* frequencyHz= */ 1, /* duration= */ 10),
+                new PrimitiveSegment(PRIMITIVE_CLICK, 1f, 0, DELAY_TYPE_PAUSE)));
+
+        assertEquals(-1, mAdapter.adaptToVibrator(BASIC_VIBRATOR_INFO, segments, -1));
+        assertEquals(expectedSegments, segments);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_PRIMITIVE_COMPOSITION_ABSOLUTE_DELAY)
+    public void testPrimitiveWithRelativeDelayAfter_afterUnknownDuration_usesZeroAsDuration() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new PrebakedSegment(VibrationEffect.EFFECT_POP, false,
+                        VibrationEffect.EFFECT_STRENGTH_STRONG),
+                new PrimitiveSegment(PRIMITIVE_CLICK, 1f, 10, DELAY_TYPE_RELATIVE_START_OFFSET)));
+
+        assertEquals(-1, mAdapter.adaptToVibrator(BASIC_VIBRATOR_INFO, segments, -1));
+
+        List<VibrationEffectSegment> expectedSegments = new ArrayList<>(Arrays.asList(
+                new PrebakedSegment(VibrationEffect.EFFECT_POP, false,
+                        VibrationEffect.EFFECT_STRENGTH_STRONG),
+                new PrimitiveSegment(PRIMITIVE_CLICK, 1f, 10, DELAY_TYPE_PAUSE)));
+
+        assertEquals(expectedSegments, segments);
+    }
+
+    private static VibratorInfo createVibratorInfoWithPrimitives(int[] ids, int[] durations) {
+        VibratorInfo.Builder builder = new VibratorInfo.Builder(0)
+                .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+
+        for (int i = 0; i < ids.length; i++) {
+            builder.setSupportedPrimitive(ids[i], durations[i]);
+        }
+
+        return builder.build();
+    }
+}
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 0933590..3c2f961 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -754,7 +754,7 @@
         HalVibration vibration = startThreadAndDispatcher(effect);
         waitForCompletion();
 
-        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(0L));
+        verify(mManagerHooks, never()).noteVibratorOn(eq(UID), anyLong());
         verify(mManagerHooks, never()).noteVibratorOff(eq(UID));
         verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
         verifyCallbacksTriggered(vibration, Status.IGNORED_UNSUPPORTED);
@@ -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/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 88ba9e3..5f76d68 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -1531,6 +1531,8 @@
         FakeVibratorControllerProvider fakeVibrator1 = mVibratorProviders.get(1);
         fakeVibrator1.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
         mVibratorProviders.get(2).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        mVibratorProviders.get(2).setSupportedPrimitives(
+                VibrationEffect.Composition.PRIMITIVE_CLICK);
         VibratorManagerService service = createSystemReadyService();
 
         CombinedVibration effect = CombinedVibration.startParallel()
@@ -2115,7 +2117,8 @@
         mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
         mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
         mVibratorProviders.get(1).setSupportedPrimitives(
-                VibrationEffect.Composition.PRIMITIVE_CLICK);
+                VibrationEffect.Composition.PRIMITIVE_CLICK,
+                VibrationEffect.Composition.PRIMITIVE_TICK);
 
         VibratorManagerService service = createSystemReadyService();
         vibrateAndWaitUntilFinished(service,
@@ -2132,9 +2135,10 @@
         assertTrue(segments.size() > 2);
         // 0: Supported effect played
         assertTrue(segments.get(0) instanceof PrebakedSegment);
-        // 1: No segment for unsupported primitive
+        // 1: Supported primitive played
+        assertTrue(segments.get(1) instanceof PrimitiveSegment);
         // 2: One or more intermediate step segments as fallback for unsupported effect
-        for (int i = 1; i < segments.size() - 1; i++) {
+        for (int i = 2; i < segments.size() - 1; i++) {
             assertTrue(segments.get(i) instanceof StepSegment);
         }
         // 3: Supported primitive played
@@ -3277,7 +3281,8 @@
         mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
         mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
         mVibratorProviders.get(1).setSupportedPrimitives(
-                VibrationEffect.Composition.PRIMITIVE_TICK);
+                VibrationEffect.Composition.PRIMITIVE_TICK,
+                VibrationEffect.Composition.PRIMITIVE_CLICK);
 
         VibratorManagerService service = createSystemReadyService();
         vibrateAndWaitUntilFinished(service,
@@ -3320,10 +3325,12 @@
         assertEquals(3, metrics.halPerformCount); // CLICK, TICK, then CLICK
         assertEquals(4, metrics.halCompositionSize); // 2*TICK + 2*CLICK
         // No repetitions in reported effect/primitive IDs.
-        assertArrayEquals(new int[] {VibrationEffect.Composition.PRIMITIVE_TICK},
+        assertArrayEquals(
+                new int[] {
+                        VibrationEffect.Composition.PRIMITIVE_CLICK,
+                        VibrationEffect.Composition.PRIMITIVE_TICK,
+                },
                 metrics.halSupportedCompositionPrimitivesUsed);
-        assertArrayEquals(new int[] {VibrationEffect.Composition.PRIMITIVE_CLICK},
-                metrics.halUnsupportedCompositionPrimitivesUsed);
         assertArrayEquals(new int[] {VibrationEffect.EFFECT_CLICK},
                 metrics.halSupportedEffectsUsed);
         assertArrayEquals(new int[] {VibrationEffect.EFFECT_TICK},
diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
index 4dc59c2..3f34767 100644
--- a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
+++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
@@ -236,7 +236,7 @@
             infoBuilder.setSupportedEffects(mSupportedEffects);
             if (mSupportedPrimitives != null) {
                 for (int primitive : mSupportedPrimitives) {
-                    infoBuilder.setSupportedPrimitive(primitive, EFFECT_DURATION);
+                    infoBuilder.setSupportedPrimitive(primitive, (int) mPrimitiveDuration);
                 }
             }
             infoBuilder.setCompositionSizeMax(mCompositionSizeMax);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 1750a14..b737d35 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -353,6 +353,29 @@
     }
 
     @Test
+    public void testDestroySurface() {
+        final WindowState win = createWindow(null, TYPE_APPLICATION, "win");
+        win.mHasSurface = win.mAnimatingExit = true;
+        win.mWinAnimator.mSurfaceControl = mock(SurfaceControl.class);
+        win.onExitAnimationDone();
+
+        assertFalse("Case 1 destroySurface no-op",
+                win.destroySurface(false /* cleanupOnResume */, false /* appStopped */));
+        assertTrue(win.mHasSurface);
+        assertTrue(win.mDestroying);
+
+        assertFalse("Case 2 destroySurface no-op",
+                win.destroySurface(true /* cleanupOnResume */, false /* appStopped */));
+        assertTrue(win.mHasSurface);
+        assertTrue(win.mDestroying);
+
+        assertTrue("Case 3 destroySurface destroys surface",
+                win.destroySurface(false /* cleanupOnResume */, true /* appStopped */));
+        assertFalse(win.mDestroying);
+        assertFalse(win.mHasSurface);
+    }
+
+    @Test
     public void testPrepareWindowToDisplayDuringRelayout() {
         // Call prepareWindowToDisplayDuringRelayout for a window without FLAG_TURN_SCREEN_ON before
         // calling setCurrentLaunchCanTurnScreenOn for windows with flag in the same activity.
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
index ea61ad9..9f5e6d1 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
@@ -347,6 +347,14 @@
         waitForTransitionToFullscreen(wmHelper)
     }
 
+    /** Maximize an app by dragging the app handle to the top drag zone. */
+    fun maximizeAppWithDragToTopDragZone(
+        wmHelper: WindowManagerStateHelper,
+        device: UiDevice,
+    ) {
+        dragAppWindowToTopDragZone(wmHelper, device)
+    }
+
     private fun dragAppWindowToTopDragZone(wmHelper: WindowManagerStateHelper, device: UiDevice) {
         val windowRect = wmHelper.getWindowRegion(innerHelper).bounds
         val displayRect = getDisplayRect(wmHelper)
diff --git a/tests/broadcasts/OWNERS b/tests/broadcasts/OWNERS
deleted file mode 100644
index d2e1f81..0000000
--- a/tests/broadcasts/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# Bug component: 316181
-include platform/frameworks/base:/BROADCASTS_OWNERS
diff --git a/tests/broadcasts/unit/Android.bp b/tests/broadcasts/unit/Android.bp
deleted file mode 100644
index 47166a7..0000000
--- a/tests/broadcasts/unit/Android.bp
+++ /dev/null
@@ -1,45 +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 {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_base_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_base_license"],
-    default_team: "trendy_team_framework_backstage_power",
-}
-
-android_test {
-    name: "BroadcastUnitTests",
-    srcs: ["src/**/*.java"],
-    defaults: [
-        "modules-utils-extended-mockito-rule-defaults",
-    ],
-    static_libs: [
-        "androidx.test.runner",
-        "androidx.test.rules",
-        "androidx.test.ext.junit",
-        "mockito-target-extended-minus-junit4",
-        "truth",
-        "flag-junit",
-        "android.app.flags-aconfig-java",
-    ],
-    certificate: "platform",
-    platform_apis: true,
-    test_suites: ["device-tests"],
-}
diff --git a/tests/broadcasts/unit/AndroidManifest.xml b/tests/broadcasts/unit/AndroidManifest.xml
deleted file mode 100644
index e9c5248..0000000
--- a/tests/broadcasts/unit/AndroidManifest.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.broadcasts.unit" >
-
-    <application android:debuggable="true">
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-            android:targetPackage="com.android.broadcasts.unit"
-            android:label="Broadcasts Unit Tests"/>
-</manifest>
\ No newline at end of file
diff --git a/tests/broadcasts/unit/AndroidTest.xml b/tests/broadcasts/unit/AndroidTest.xml
deleted file mode 100644
index b91e4783..0000000
--- a/tests/broadcasts/unit/AndroidTest.xml
+++ /dev/null
@@ -1,29 +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.
--->
-<configuration description="Runs Broadcasts tests">
-    <option name="test-suite-tag" value="apct" />
-    <option name="test-tag" value="BroadcastUnitTests" />
-
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true" />
-        <option name="test-file-name" value="BroadcastUnitTests.apk" />
-    </target_preparer>
-
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
-        <option name="package" value="com.android.broadcasts.unit" />
-        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
-        <option name="hidden-api-checks" value="false"/>
-    </test>
-</configuration>
\ No newline at end of file
diff --git a/tests/broadcasts/unit/TEST_MAPPING b/tests/broadcasts/unit/TEST_MAPPING
deleted file mode 100644
index 8919fdc..0000000
--- a/tests/broadcasts/unit/TEST_MAPPING
+++ /dev/null
@@ -1,15 +0,0 @@
-{
-    "presubmit": [
-        {
-            "name": "BroadcastUnitTests",
-            "options": [
-                {
-                    "exclude-annotation": "androidx.test.filters.FlakyTest"
-                },
-                {
-                    "exclude-annotation": "org.junit.Ignore"
-                }
-            ]
-        }
-    ]
-}
diff --git a/tests/broadcasts/unit/src/android/app/BroadcastStickyCacheTest.java b/tests/broadcasts/unit/src/android/app/BroadcastStickyCacheTest.java
deleted file mode 100644
index b7c412d..0000000
--- a/tests/broadcasts/unit/src/android/app/BroadcastStickyCacheTest.java
+++ /dev/null
@@ -1,258 +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 android.app;
-
-import static android.content.Intent.ACTION_BATTERY_CHANGED;
-import static android.content.Intent.ACTION_DEVICE_STORAGE_LOW;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.anyString;
-
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.BatteryManager;
-import android.os.Bundle;
-import android.os.SystemProperties;
-import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
-import android.util.ArrayMap;
-
-import androidx.annotation.GuardedBy;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.modules.utils.testing.ExtendedMockitoRule;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.ClassRule;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mockito;
-
-@EnableFlags(Flags.FLAG_USE_STICKY_BCAST_CACHE)
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class BroadcastStickyCacheTest {
-    @ClassRule
-    public static final SetFlagsRule.ClassRule mClassRule = new SetFlagsRule.ClassRule();
-    @Rule
-    public final SetFlagsRule mSetFlagsRule = mClassRule.createSetFlagsRule();
-
-    @Rule
-    public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
-            .mockStatic(SystemProperties.class)
-            .build();
-
-    private static final String PROP_KEY_BATTERY_CHANGED = BroadcastStickyCache.getKey(
-            ACTION_BATTERY_CHANGED);
-
-    private final TestSystemProps mTestSystemProps = new TestSystemProps();
-
-    @Before
-    public void setUp() {
-        doAnswer(invocation -> {
-            final String name = invocation.getArgument(0);
-            final long value = Long.parseLong(invocation.getArgument(1));
-            mTestSystemProps.add(name, value);
-            return null;
-        }).when(() -> SystemProperties.set(anyString(), anyString()));
-        doAnswer(invocation -> {
-            final String name = invocation.getArgument(0);
-            final TestSystemProps.Handle testHandle = mTestSystemProps.query(name);
-            if (testHandle == null) {
-                return null;
-            }
-            final SystemProperties.Handle handle = Mockito.mock(SystemProperties.Handle.class);
-            doAnswer(handleInvocation -> testHandle.getLong(-1)).when(handle).getLong(anyLong());
-            return handle;
-        }).when(() -> SystemProperties.find(anyString()));
-    }
-
-    @After
-    public void tearDown() {
-        mTestSystemProps.clear();
-        BroadcastStickyCache.clearForTest();
-    }
-
-    @Test
-    public void testUseCache_nullFilter() {
-        assertThat(BroadcastStickyCache.useCache(null)).isEqualTo(false);
-    }
-
-    @Test
-    public void testUseCache_noActions() {
-        final IntentFilter filter = new IntentFilter();
-        assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(false);
-    }
-
-    @Test
-    public void testUseCache_multipleActions() {
-        final IntentFilter filter = new IntentFilter();
-        filter.addAction(ACTION_DEVICE_STORAGE_LOW);
-        filter.addAction(ACTION_BATTERY_CHANGED);
-        assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(false);
-    }
-
-    @Test
-    public void testUseCache_valueNotSet() {
-        final IntentFilter filter = new IntentFilter(ACTION_BATTERY_CHANGED);
-        assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(false);
-    }
-
-    @Test
-    public void testUseCache() {
-        final IntentFilter filter = new IntentFilter(ACTION_BATTERY_CHANGED);
-        final Intent intent = new Intent(ACTION_BATTERY_CHANGED)
-                .putExtra(BatteryManager.EXTRA_LEVEL, 90);
-        BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
-        BroadcastStickyCache.add(filter, intent);
-        assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(true);
-    }
-
-    @Test
-    public void testUseCache_versionMismatch() {
-        final IntentFilter filter = new IntentFilter(ACTION_BATTERY_CHANGED);
-        final Intent intent = new Intent(ACTION_BATTERY_CHANGED)
-                .putExtra(BatteryManager.EXTRA_LEVEL, 90);
-        BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
-        BroadcastStickyCache.add(filter, intent);
-        BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
-
-        assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(false);
-    }
-
-    @Test
-    public void testAdd() {
-        final IntentFilter filter = new IntentFilter(ACTION_BATTERY_CHANGED);
-        Intent intent = new Intent(ACTION_BATTERY_CHANGED)
-                .putExtra(BatteryManager.EXTRA_LEVEL, 90);
-        BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
-        BroadcastStickyCache.add(filter, intent);
-        assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(true);
-        Intent actualIntent = BroadcastStickyCache.getIntentUnchecked(filter);
-        assertThat(actualIntent).isNotNull();
-        assertEquals(actualIntent, intent);
-
-        intent = new Intent(ACTION_BATTERY_CHANGED)
-                .putExtra(BatteryManager.EXTRA_LEVEL, 99);
-        BroadcastStickyCache.add(filter, intent);
-        actualIntent = BroadcastStickyCache.getIntentUnchecked(filter);
-        assertThat(actualIntent).isNotNull();
-        assertEquals(actualIntent, intent);
-    }
-
-    @Test
-    public void testIncrementVersion_propExists() {
-        SystemProperties.set(PROP_KEY_BATTERY_CHANGED, String.valueOf(100));
-
-        BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
-        assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(101);
-        BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
-        assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(102);
-    }
-
-    @Test
-    public void testIncrementVersion_propNotExists() {
-        // Verify that the property doesn't exist
-        assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(-1);
-
-        BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
-        assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(1);
-        BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
-        assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(2);
-    }
-
-    @Test
-    public void testIncrementVersionIfExists_propExists() {
-        BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED);
-
-        BroadcastStickyCache.incrementVersionIfExists(ACTION_BATTERY_CHANGED);
-        assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(2);
-        BroadcastStickyCache.incrementVersionIfExists(ACTION_BATTERY_CHANGED);
-        assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(3);
-    }
-
-    @Test
-    public void testIncrementVersionIfExists_propNotExists() {
-        // Verify that the property doesn't exist
-        assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(-1);
-
-        BroadcastStickyCache.incrementVersionIfExists(ACTION_BATTERY_CHANGED);
-        assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(-1);
-        // Verify that property is not added as part of the querying.
-        BroadcastStickyCache.incrementVersionIfExists(ACTION_BATTERY_CHANGED);
-        assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(-1);
-    }
-
-    private void assertEquals(Intent actualIntent, Intent expectedIntent) {
-        assertThat(actualIntent.getAction()).isEqualTo(expectedIntent.getAction());
-        assertEquals(actualIntent.getExtras(), expectedIntent.getExtras());
-    }
-
-    private void assertEquals(Bundle actualExtras, Bundle expectedExtras) {
-        assertWithMessage("Extras expected=%s, actual=%s", expectedExtras, actualExtras)
-                .that(actualExtras.kindofEquals(expectedExtras)).isTrue();
-    }
-
-    private static final class TestSystemProps {
-        @GuardedBy("mSysProps")
-        private final ArrayMap<String, Long> mSysProps = new ArrayMap<>();
-
-        public void add(String name, long value) {
-            synchronized (mSysProps) {
-                mSysProps.put(name, value);
-            }
-        }
-
-        public long get(String name, long defaultValue) {
-            synchronized (mSysProps) {
-                final int idx = mSysProps.indexOfKey(name);
-                return idx >= 0 ? mSysProps.valueAt(idx) : defaultValue;
-            }
-        }
-
-        public Handle query(String name) {
-            synchronized (mSysProps) {
-                return mSysProps.containsKey(name) ? new Handle(name) : null;
-            }
-        }
-
-        public void clear() {
-            synchronized (mSysProps) {
-                mSysProps.clear();
-            }
-        }
-
-        public class Handle {
-            private final String mName;
-
-            Handle(String name) {
-                mName = name;
-            }
-
-            public long getLong(long defaultValue) {
-                return get(mName, defaultValue);
-            }
-        }
-    }
-}