Merge changes If52c45a9,I203862ae,I94523ba2,I21b8095f,I65abed1c into main

* changes:
  Create ShadeStateTraceLogger
  Group status bar related instant event under a track group
  Group scrim related instant event under a track group
  Group notification related instant event under a track group
  Group shade related instant event under a track group
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 22af517..93f3119 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -18570,13 +18570,13 @@
 
   @FlaggedApi("com.android.internal.telephony.flags.satellite_state_change_listener") public final class SatelliteManager {
     method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void addAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
-    method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void deprovisionSatellite(@NonNull java.util.List<android.telephony.satellite.SatelliteSubscriberInfo>, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
+    method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void deprovisionSatellite(@NonNull java.util.List<android.telephony.satellite.SatelliteSubscriberInfo>, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void deprovisionService(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.Set<java.lang.Integer> getAttachRestrictionReasonsForCarrier(int);
     method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int[] getSatelliteDisallowedReasons();
     method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.List<java.lang.String> getSatellitePlmnsForCarrier(int);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingDatagrams(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
-    method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionSatellite(@NonNull java.util.List<android.telephony.satellite.SatelliteSubscriberInfo>, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
+    method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionSatellite(@NonNull java.util.List<android.telephony.satellite.SatelliteSubscriberInfo>, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionService(@NonNull String, @NonNull byte[], @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForCapabilitiesChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteCapabilitiesCallback);
     method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForCommunicationAccessStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteCommunicationAccessStateCallback);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index a988acf..a352d9d 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3415,6 +3415,10 @@
     method public void onBindClient(@Nullable android.content.Intent);
   }
 
+  public class TelecomManager {
+    method @FlaggedApi("com.android.server.telecom.flags.voip_call_monitor_refactor") public boolean hasForegroundServiceDelegation(@Nullable android.telecom.PhoneAccountHandle);
+  }
+
 }
 
 package android.telephony {
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 614e2aa..5176aee 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5856,7 +5856,9 @@
                 return null;
             }
             final int size = mContext.getResources().getDimensionPixelSize(
-                    R.dimen.notification_badge_size);
+                    Flags.notificationsRedesignTemplates()
+                            ? R.dimen.notification_2025_badge_size
+                            : R.dimen.notification_badge_size);
             Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
             Canvas canvas = new Canvas(bitmap);
             badge.setBounds(0, 0, size, size);
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 08bd854..aede8aa 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -17,6 +17,7 @@
 package android.app;
 
 import static android.Manifest.permission.POST_NOTIFICATIONS;
+import static android.app.NotificationChannel.DEFAULT_CHANNEL_ID;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.service.notification.Flags.notificationClassification;
 
@@ -50,6 +51,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.IpcDataCache;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.RemoteException;
@@ -71,6 +73,8 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.time.InstantSource;
@@ -1202,12 +1206,20 @@
      * package (see {@link Context#createPackageContext(String, int)}).</p>
      */
     public NotificationChannel getNotificationChannel(String channelId) {
-        INotificationManager service = service();
-        try {
-            return service.getNotificationChannel(mContext.getOpPackageName(),
-                    mContext.getUserId(), mContext.getPackageName(), channelId);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
+        if (Flags.nmBinderPerfCacheChannels()) {
+            return getChannelFromList(channelId,
+                    mNotificationChannelListCache.query(new NotificationChannelQuery(
+                            mContext.getOpPackageName(),
+                            mContext.getPackageName(),
+                            mContext.getUserId())));
+        } else {
+            INotificationManager service = service();
+            try {
+                return service.getNotificationChannel(mContext.getOpPackageName(),
+                        mContext.getUserId(), mContext.getPackageName(), channelId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
         }
     }
 
@@ -1222,13 +1234,21 @@
      */
     public @Nullable NotificationChannel getNotificationChannel(@NonNull String channelId,
             @NonNull String conversationId) {
-        INotificationManager service = service();
-        try {
-            return service.getConversationNotificationChannel(mContext.getOpPackageName(),
-                    mContext.getUserId(), mContext.getPackageName(), channelId, true,
-                    conversationId);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
+        if (Flags.nmBinderPerfCacheChannels()) {
+            return getConversationChannelFromList(channelId, conversationId,
+                    mNotificationChannelListCache.query(new NotificationChannelQuery(
+                            mContext.getOpPackageName(),
+                            mContext.getPackageName(),
+                            mContext.getUserId())));
+        } else {
+            INotificationManager service = service();
+            try {
+                return service.getConversationNotificationChannel(mContext.getOpPackageName(),
+                        mContext.getUserId(), mContext.getPackageName(), channelId, true,
+                        conversationId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
         }
     }
 
@@ -1241,15 +1261,62 @@
      * {@link Context#createPackageContext(String, int)}).</p>
      */
     public List<NotificationChannel> getNotificationChannels() {
-        INotificationManager service = service();
-        try {
-            return service.getNotificationChannels(mContext.getOpPackageName(),
-                    mContext.getPackageName(), mContext.getUserId()).getList();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
+        if (Flags.nmBinderPerfCacheChannels()) {
+            return mNotificationChannelListCache.query(new NotificationChannelQuery(
+               mContext.getOpPackageName(),
+               mContext.getPackageName(),
+               mContext.getUserId()));
+        } else {
+            INotificationManager service = service();
+            try {
+                return service.getNotificationChannels(mContext.getOpPackageName(),
+                        mContext.getPackageName(), mContext.getUserId()).getList();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
         }
     }
 
+    // channel list assumed to be associated with the appropriate package & user id already.
+    private static NotificationChannel getChannelFromList(String channelId,
+            List<NotificationChannel> channels) {
+        if (channels == null) {
+            return null;
+        }
+        if (channelId == null) {
+            channelId = DEFAULT_CHANNEL_ID;
+        }
+        for (NotificationChannel channel : channels) {
+            if (channelId.equals(channel.getId())) {
+                return channel;
+            }
+        }
+        return null;
+    }
+
+    private static NotificationChannel getConversationChannelFromList(String channelId,
+            String conversationId, List<NotificationChannel> channels) {
+        if (channels == null) {
+            return null;
+        }
+        if (channelId == null) {
+            channelId = DEFAULT_CHANNEL_ID;
+        }
+        if (conversationId == null) {
+            return getChannelFromList(channelId, channels);
+        }
+        NotificationChannel parent = null;
+        for (NotificationChannel channel : channels) {
+            if (conversationId.equals(channel.getConversationId())
+                    && channelId.equals(channel.getParentChannelId())) {
+                return channel;
+            } else if (channelId.equals(channel.getId())) {
+                parent = channel;
+            }
+        }
+        return parent;
+    }
+
     /**
      * Deletes the given notification channel.
      *
@@ -1328,6 +1395,71 @@
         }
     }
 
+    private static final String NOTIFICATION_CHANNEL_CACHE_API = "getNotificationChannel";
+    private static final String NOTIFICATION_CHANNEL_LIST_CACHE_NAME = "getNotificationChannels";
+    private static final int NOTIFICATION_CHANNEL_CACHE_SIZE = 10;
+
+    private final IpcDataCache.QueryHandler<NotificationChannelQuery, List<NotificationChannel>>
+            mNotificationChannelListQueryHandler = new IpcDataCache.QueryHandler<>() {
+                @Override
+                public List<NotificationChannel> apply(NotificationChannelQuery query) {
+                    INotificationManager service = service();
+                    try {
+                        return service.getNotificationChannels(query.callingPkg,
+                                query.targetPkg, query.userId).getList();
+                    } catch (RemoteException e) {
+                        throw e.rethrowFromSystemServer();
+                    }
+                }
+
+                @Override
+                public boolean shouldBypassCache(@NonNull NotificationChannelQuery query) {
+                    // Other locations should also not be querying the cache in the first place if
+                    // the flag is not enabled, but this is an extra precaution.
+                    if (!Flags.nmBinderPerfCacheChannels()) {
+                        Log.wtf(TAG,
+                                "shouldBypassCache called when nm_binder_perf_cache_channels off");
+                        return true;
+                    }
+                    return false;
+                }
+            };
+
+    private final IpcDataCache<NotificationChannelQuery, List<NotificationChannel>>
+            mNotificationChannelListCache =
+            new IpcDataCache<>(NOTIFICATION_CHANNEL_CACHE_SIZE, IpcDataCache.MODULE_SYSTEM,
+                    NOTIFICATION_CHANNEL_CACHE_API, NOTIFICATION_CHANNEL_LIST_CACHE_NAME,
+                    mNotificationChannelListQueryHandler);
+
+    private record NotificationChannelQuery(
+            String callingPkg,
+            String targetPkg,
+            int userId) {}
+
+    /**
+     * @hide
+     */
+    public static void invalidateNotificationChannelCache() {
+        if (Flags.nmBinderPerfCacheChannels()) {
+            IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM,
+                    NOTIFICATION_CHANNEL_CACHE_API);
+        } else {
+            // if we are here, we have failed to flag something
+            Log.wtf(TAG, "invalidateNotificationChannelCache called without flag");
+        }
+    }
+
+    /**
+     * For testing only: running tests with a cache requires marking the cache's property for
+     * testing, as test APIs otherwise cannot invalidate the cache. This must be called after
+     * calling PropertyInvalidatedCache.setTestMode(true).
+     * @hide
+     */
+    @VisibleForTesting
+    public void setChannelCacheToTestMode() {
+        mNotificationChannelListCache.testPropertyName();
+    }
+
     /**
      * @hide
      */
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index a2fddb0..c504521 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -4354,20 +4354,24 @@
     }
 
     /**
-     * Indicates that app functions are not controlled by policy.
+     * Indicates that {@link android.app.appfunctions.AppFunctionManager} is not controlled by
+     * policy.
      *
      * <p>If no admin set this policy, it means appfunctions are enabled.
      */
     @FlaggedApi(android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER)
     public static final int APP_FUNCTIONS_NOT_CONTROLLED_BY_POLICY = 0;
 
-    /** Indicates that app functions are controlled and disabled by a policy. */
+    /** Indicates that {@link android.app.appfunctions.AppFunctionManager} is controlled and
+     * disabled by policy, i.e. no apps in the current user are allowed to expose app functions.
+     */
     @FlaggedApi(android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER)
     public static final int APP_FUNCTIONS_DISABLED = 1;
 
     /**
-     * Indicates that app functions are controlled and disabled by a policy for cross profile
-     * interactions only.
+     * Indicates that {@link android.app.appfunctions.AppFunctionManager} is controlled and
+     * disabled by a policy for cross profile interactions only, i.e. app functions exposed by apps
+     * in the current user can only be invoked within the same user.
      *
      * <p>This is different from {@link #APP_FUNCTIONS_DISABLED} in that it only disables cross
      * profile interactions (even if the caller has permissions required to interact across users).
@@ -4388,7 +4392,9 @@
     public @interface AppFunctionsPolicy {}
 
     /**
-     * Sets the app functions policy which controls app functions operations on the device.
+     * Sets the {@link android.app.appfunctions.AppFunctionManager} policy which controls app
+     * functions operations on the device. An app function is a piece of functionality that apps
+     * expose to the system for cross-app orchestration.
      *
      * <p>This function can only be called by a device owner, a profile owner or holders of the
      * permission {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_APP_FUNCTIONS}.
@@ -4414,7 +4420,7 @@
     }
 
     /**
-     * Returns the current app functions policy.
+     * Returns the current {@link android.app.appfunctions.AppFunctionManager} policy.
      *
      * <p>The returned policy will be the current resolved policy rather than the policy set by the
      * calling admin.
diff --git a/core/java/android/app/time/TimeZoneCapabilities.java b/core/java/android/app/time/TimeZoneCapabilities.java
index 4dee159..929f660 100644
--- a/core/java/android/app/time/TimeZoneCapabilities.java
+++ b/core/java/android/app/time/TimeZoneCapabilities.java
@@ -55,7 +55,8 @@
      * The user the capabilities are for. This is used for object equality and debugging but there
      * is no accessor.
      */
-    @NonNull private final UserHandle mUserHandle;
+    @NonNull
+    private final UserHandle mUserHandle;
     private final @CapabilityState int mConfigureAutoDetectionEnabledCapability;
 
     /**
@@ -69,6 +70,7 @@
 
     private final @CapabilityState int mConfigureGeoDetectionEnabledCapability;
     private final @CapabilityState int mSetManualTimeZoneCapability;
+    private final @CapabilityState int mConfigureNotificationsEnabledCapability;
 
     private TimeZoneCapabilities(@NonNull Builder builder) {
         this.mUserHandle = Objects.requireNonNull(builder.mUserHandle);
@@ -78,6 +80,8 @@
         this.mConfigureGeoDetectionEnabledCapability =
                 builder.mConfigureGeoDetectionEnabledCapability;
         this.mSetManualTimeZoneCapability = builder.mSetManualTimeZoneCapability;
+        this.mConfigureNotificationsEnabledCapability =
+                builder.mConfigureNotificationsEnabledCapability;
     }
 
     @NonNull
@@ -88,6 +92,7 @@
                 .setUseLocationEnabled(in.readBoolean())
                 .setConfigureGeoDetectionEnabledCapability(in.readInt())
                 .setSetManualTimeZoneCapability(in.readInt())
+                .setConfigureNotificationsEnabledCapability(in.readInt())
                 .build();
     }
 
@@ -98,6 +103,7 @@
         dest.writeBoolean(mUseLocationEnabled);
         dest.writeInt(mConfigureGeoDetectionEnabledCapability);
         dest.writeInt(mSetManualTimeZoneCapability);
+        dest.writeInt(mConfigureNotificationsEnabledCapability);
     }
 
     /**
@@ -117,8 +123,8 @@
      *
      * Not part of the SDK API because it is intended for use by SettingsUI, which can display
      * text about needing it to be on for location-based time zone detection.
-     * @hide
      *
+     * @hide
      */
     public boolean isUseLocationEnabled() {
         return mUseLocationEnabled;
@@ -148,6 +154,18 @@
     }
 
     /**
+     * Returns the capability state associated with the user's ability to modify the time zone
+     * notification setting. The setting can be updated via {@link
+     * TimeManager#updateTimeZoneConfiguration(TimeZoneConfiguration)}.
+     *
+     * @hide
+     */
+    @CapabilityState
+    public int getConfigureNotificationsEnabledCapability() {
+        return mConfigureNotificationsEnabledCapability;
+    }
+
+    /**
      * Tries to create a new {@link TimeZoneConfiguration} from the {@code config} and the set of
      * {@code requestedChanges}, if {@code this} capabilities allow. The new configuration is
      * returned. If the capabilities do not permit one or more of the requested changes then {@code
@@ -174,6 +192,12 @@
             newConfigBuilder.setGeoDetectionEnabled(requestedChanges.isGeoDetectionEnabled());
         }
 
+        if (requestedChanges.hasIsNotificationsEnabled()) {
+            if (this.getConfigureNotificationsEnabledCapability() < CAPABILITY_NOT_APPLICABLE) {
+                return null;
+            }
+            newConfigBuilder.setNotificationsEnabled(requestedChanges.areNotificationsEnabled());
+        }
         return newConfigBuilder.build();
     }
 
@@ -197,13 +221,16 @@
                 && mUseLocationEnabled == that.mUseLocationEnabled
                 && mConfigureGeoDetectionEnabledCapability
                 == that.mConfigureGeoDetectionEnabledCapability
-                && mSetManualTimeZoneCapability == that.mSetManualTimeZoneCapability;
+                && mSetManualTimeZoneCapability == that.mSetManualTimeZoneCapability
+                && mConfigureNotificationsEnabledCapability
+                == that.mConfigureNotificationsEnabledCapability;
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(mUserHandle, mConfigureAutoDetectionEnabledCapability,
-                mConfigureGeoDetectionEnabledCapability, mSetManualTimeZoneCapability);
+                mConfigureGeoDetectionEnabledCapability, mSetManualTimeZoneCapability,
+                mConfigureNotificationsEnabledCapability);
     }
 
     @Override
@@ -216,6 +243,8 @@
                 + ", mConfigureGeoDetectionEnabledCapability="
                 + mConfigureGeoDetectionEnabledCapability
                 + ", mSetManualTimeZoneCapability=" + mSetManualTimeZoneCapability
+                + ", mConfigureNotificationsEnabledCapability="
+                + mConfigureNotificationsEnabledCapability
                 + '}';
     }
 
@@ -226,11 +255,13 @@
      */
     public static class Builder {
 
-        @NonNull private UserHandle mUserHandle;
+        @NonNull
+        private UserHandle mUserHandle;
         private @CapabilityState int mConfigureAutoDetectionEnabledCapability;
         private Boolean mUseLocationEnabled;
         private @CapabilityState int mConfigureGeoDetectionEnabledCapability;
         private @CapabilityState int mSetManualTimeZoneCapability;
+        private @CapabilityState int mConfigureNotificationsEnabledCapability;
 
         public Builder(@NonNull UserHandle userHandle) {
             mUserHandle = Objects.requireNonNull(userHandle);
@@ -240,12 +271,14 @@
             Objects.requireNonNull(capabilitiesToCopy);
             mUserHandle = capabilitiesToCopy.mUserHandle;
             mConfigureAutoDetectionEnabledCapability =
-                capabilitiesToCopy.mConfigureAutoDetectionEnabledCapability;
+                    capabilitiesToCopy.mConfigureAutoDetectionEnabledCapability;
             mUseLocationEnabled = capabilitiesToCopy.mUseLocationEnabled;
             mConfigureGeoDetectionEnabledCapability =
-                capabilitiesToCopy.mConfigureGeoDetectionEnabledCapability;
+                    capabilitiesToCopy.mConfigureGeoDetectionEnabledCapability;
             mSetManualTimeZoneCapability =
-                capabilitiesToCopy.mSetManualTimeZoneCapability;
+                    capabilitiesToCopy.mSetManualTimeZoneCapability;
+            mConfigureNotificationsEnabledCapability =
+                    capabilitiesToCopy.mConfigureNotificationsEnabledCapability;
         }
 
         /** Sets the value for the "configure automatic time zone detection enabled" capability. */
@@ -274,6 +307,14 @@
             return this;
         }
 
+        /**
+         * Sets the value for the "configure time notifications enabled" capability.
+         */
+        public Builder setConfigureNotificationsEnabledCapability(@CapabilityState int value) {
+            this.mConfigureNotificationsEnabledCapability = value;
+            return this;
+        }
+
         /** Returns the {@link TimeZoneCapabilities}. */
         @NonNull
         public TimeZoneCapabilities build() {
@@ -283,7 +324,9 @@
             verifyCapabilitySet(mConfigureGeoDetectionEnabledCapability,
                     "configureGeoDetectionEnabledCapability");
             verifyCapabilitySet(mSetManualTimeZoneCapability,
-                    "mSetManualTimeZoneCapability");
+                    "setManualTimeZoneCapability");
+            verifyCapabilitySet(mConfigureNotificationsEnabledCapability,
+                    "configureNotificationsEnabledCapability");
             return new TimeZoneCapabilities(this);
         }
 
diff --git a/core/java/android/app/time/TimeZoneConfiguration.java b/core/java/android/app/time/TimeZoneConfiguration.java
index 7403c12..68c090f 100644
--- a/core/java/android/app/time/TimeZoneConfiguration.java
+++ b/core/java/android/app/time/TimeZoneConfiguration.java
@@ -62,7 +62,8 @@
      *
      * @hide
      */
-    @StringDef({ SETTING_AUTO_DETECTION_ENABLED, SETTING_GEO_DETECTION_ENABLED })
+    @StringDef({SETTING_AUTO_DETECTION_ENABLED, SETTING_GEO_DETECTION_ENABLED,
+            SETTING_NOTIFICATIONS_ENABLED})
     @Retention(RetentionPolicy.SOURCE)
     @interface Setting {}
 
@@ -74,6 +75,10 @@
     @Setting
     private static final String SETTING_GEO_DETECTION_ENABLED = "geoDetectionEnabled";
 
+    /** See {@link TimeZoneConfiguration#areNotificationsEnabled()} for details. */
+    @Setting
+    private static final String SETTING_NOTIFICATIONS_ENABLED = "notificationsEnabled";
+
     @NonNull private final Bundle mBundle;
 
     private TimeZoneConfiguration(Builder builder) {
@@ -98,7 +103,8 @@
      */
     public boolean isComplete() {
         return hasIsAutoDetectionEnabled()
-                && hasIsGeoDetectionEnabled();
+                && hasIsGeoDetectionEnabled()
+                && hasIsNotificationsEnabled();
     }
 
     /**
@@ -128,8 +134,7 @@
     /**
      * Returns the value of the {@link #SETTING_GEO_DETECTION_ENABLED} setting. This
      * controls whether the device can use geolocation to determine time zone. This value may only
-     * be used by Android under some circumstances. For example, it is not used when
-     * {@link #isGeoDetectionEnabled()} is {@code false}.
+     * be used by Android under some circumstances.
      *
      * <p>See {@link TimeZoneCapabilities#getConfigureGeoDetectionEnabledCapability()} for how to
      * tell if the setting is meaningful for the current user at this time.
@@ -150,6 +155,32 @@
         return mBundle.containsKey(SETTING_GEO_DETECTION_ENABLED);
     }
 
+    /**
+     * Returns the value of the {@link #SETTING_NOTIFICATIONS_ENABLED} setting. This controls
+     * whether the device can send time and time zone related notifications. This value may only
+     * be used by Android under some circumstances.
+     *
+     * <p>See {@link TimeZoneCapabilities#getConfigureNotificationsEnabledCapability()} ()} for how
+     * to tell if the setting is meaningful for the current user at this time.
+     *
+     * @throws IllegalStateException if the setting is not present
+     *
+     * @hide
+     */
+    public boolean areNotificationsEnabled() {
+        enforceSettingPresent(SETTING_NOTIFICATIONS_ENABLED);
+        return mBundle.getBoolean(SETTING_NOTIFICATIONS_ENABLED);
+    }
+
+    /**
+     * Returns {@code true} if the {@link #areNotificationsEnabled()} setting is present.
+     *
+     * @hide
+     */
+    public boolean hasIsNotificationsEnabled() {
+        return mBundle.containsKey(SETTING_NOTIFICATIONS_ENABLED);
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -244,6 +275,17 @@
             return this;
         }
 
+        /**
+         * Sets the state of the {@link #SETTING_NOTIFICATIONS_ENABLED} setting.         *
+         *
+         * @hide
+         */
+        @NonNull
+        public Builder setNotificationsEnabled(boolean enabled) {
+            this.mBundle.putBoolean(SETTING_NOTIFICATIONS_ENABLED, enabled);
+            return this;
+        }
+
         /** Returns the {@link TimeZoneConfiguration}. */
         @NonNull
         public TimeZoneConfiguration build() {
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index e3e1038..350048d 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -12394,14 +12394,30 @@
      * @hide
      */
     public void collectExtraIntentKeys() {
+        collectExtraIntentKeys(false);
+    }
+
+    /**
+     * Collects keys in the extra bundle whose value are intents.
+     * With these keys collected on the client side, the system server would only unparcel values
+     * of these keys and create IntentCreatorToken for them.
+     * This method could also be called from the system server side as a catch all safty net in case
+     * these keys are not collected on the client side. In that case, call it with forceUnparcel set
+     * to true since everything is parceled on the system server side.
+     *
+     * @param forceUnparcel if it is true, unparcel everything to determine if an object is an
+     *                      intent. Otherwise, do not unparcel anything.
+     * @hide
+     */
+    public void collectExtraIntentKeys(boolean forceUnparcel) {
         if (preventIntentRedirect()) {
-            collectNestedIntentKeysRecur(new ArraySet<>());
+            collectNestedIntentKeysRecur(new ArraySet<>(), forceUnparcel);
         }
     }
 
-    private void collectNestedIntentKeysRecur(Set<Intent> visited) {
+    private void collectNestedIntentKeysRecur(Set<Intent> visited, boolean forceUnparcel) {
         addExtendedFlags(EXTENDED_FLAG_NESTED_INTENT_KEYS_COLLECTED);
-        if (mExtras != null && !mExtras.isParcelled() && !mExtras.isEmpty()) {
+        if (mExtras != null && (forceUnparcel || !mExtras.isParcelled()) && !mExtras.isEmpty()) {
             for (String key : mExtras.keySet()) {
                 Object value;
                 try {
@@ -12410,23 +12426,25 @@
                     // It is okay to not collect a parceled intent since it would have been
                     // coming from another process and collected by its containing intent already
                     // in that process.
-                    if (!mExtras.isValueParceled(key)) {
+                    if (forceUnparcel || !mExtras.isValueParceled(key)) {
                         value = mExtras.get(key);
                     } else {
                         value = null;
                     }
                 } catch (BadParcelableException e) {
-                    // This probably would never happen. But just in case, simply ignore it since
-                    // it is not an intent anyway.
+                    // This may still happen if the keys are collected on the system server side, in
+                    // which case, we will try to unparcel everything. If this happens, simply
+                    // ignore it since it is not an intent anyway.
                     value = null;
                 }
                 if (value instanceof Intent intent) {
                     handleNestedIntent(intent, visited, new NestedIntentKey(
-                            NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL, key, 0));
+                                    NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL, key, 0),
+                            forceUnparcel);
                 } else if (value instanceof Parcelable[] parcelables) {
-                    handleParcelableArray(parcelables, key, visited);
+                    handleParcelableArray(parcelables, key, visited, forceUnparcel);
                 } else if (value instanceof ArrayList<?> parcelables) {
-                    handleParcelableList(parcelables, key, visited);
+                    handleParcelableList(parcelables, key, visited, forceUnparcel);
                 }
             }
         }
@@ -12436,13 +12454,15 @@
                 Intent intent = mClipData.getItemAt(i).mIntent;
                 if (intent != null && !visited.contains(intent)) {
                     handleNestedIntent(intent, visited, new NestedIntentKey(
-                            NestedIntentKey.NESTED_INTENT_KEY_TYPE_CLIP_DATA, null, i));
+                                    NestedIntentKey.NESTED_INTENT_KEY_TYPE_CLIP_DATA, null, i),
+                            forceUnparcel);
                 }
             }
         }
     }
 
-    private void handleNestedIntent(Intent intent, Set<Intent> visited, NestedIntentKey key) {
+    private void handleNestedIntent(Intent intent, Set<Intent> visited, NestedIntentKey key,
+            boolean forceUnparcel) {
         if (mCreatorTokenInfo == null) {
             mCreatorTokenInfo = new CreatorTokenInfo();
         }
@@ -12452,24 +12472,28 @@
         mCreatorTokenInfo.mNestedIntentKeys.add(key);
         if (!visited.contains(intent)) {
             visited.add(intent);
-            intent.collectNestedIntentKeysRecur(visited);
+            intent.collectNestedIntentKeysRecur(visited, forceUnparcel);
         }
     }
 
-    private void handleParcelableArray(Parcelable[] parcelables, String key, Set<Intent> visited) {
+    private void handleParcelableArray(Parcelable[] parcelables, String key, Set<Intent> visited,
+            boolean forceUnparcel) {
         for (int i = 0; i < parcelables.length; i++) {
             if (parcelables[i] instanceof Intent intent && !visited.contains(intent)) {
                 handleNestedIntent(intent, visited, new NestedIntentKey(
-                        NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_ARRAY, key, i));
+                                NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_ARRAY, key, i),
+                        forceUnparcel);
             }
         }
     }
 
-    private void handleParcelableList(ArrayList<?> parcelables, String key, Set<Intent> visited) {
+    private void handleParcelableList(ArrayList<?> parcelables, String key, Set<Intent> visited,
+            boolean forceUnparcel) {
         for (int i = 0; i < parcelables.size(); i++) {
             if (parcelables.get(i) instanceof Intent intent && !visited.contains(intent)) {
                 handleNestedIntent(intent, visited, new NestedIntentKey(
-                        NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_LIST, key, i));
+                                NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_LIST, key, i),
+                        forceUnparcel);
             }
         }
     }
diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java
index b938aac..f538e9f 100644
--- a/core/java/android/content/res/ApkAssets.java
+++ b/core/java/android/content/res/ApkAssets.java
@@ -367,7 +367,7 @@
     /** @hide */
     public @NonNull String getDebugName() {
         synchronized (this) {
-            return nativeGetDebugName(mNativePtr);
+            return mNativePtr == 0 ? "<destroyed>" : nativeGetDebugName(mNativePtr);
         }
     }
 
diff --git a/core/java/android/hardware/input/InputGestureData.java b/core/java/android/hardware/input/InputGestureData.java
index f41550f..75c652c 100644
--- a/core/java/android/hardware/input/InputGestureData.java
+++ b/core/java/android/hardware/input/InputGestureData.java
@@ -48,27 +48,7 @@
 
     /** Returns the trigger information for this input gesture */
     public Trigger getTrigger() {
-        switch (mInputGestureData.trigger.getTag()) {
-            case AidlInputGestureData.Trigger.Tag.key: {
-                AidlInputGestureData.KeyTrigger trigger = mInputGestureData.trigger.getKey();
-                if (trigger == null) {
-                    throw new RuntimeException("InputGestureData is corrupted, null key trigger!");
-                }
-                return createKeyTrigger(trigger.keycode, trigger.modifierState);
-            }
-            case AidlInputGestureData.Trigger.Tag.touchpadGesture: {
-                AidlInputGestureData.TouchpadGestureTrigger trigger =
-                        mInputGestureData.trigger.getTouchpadGesture();
-                if (trigger == null) {
-                    throw new RuntimeException(
-                            "InputGestureData is corrupted, null touchpad trigger!");
-                }
-                return createTouchpadTrigger(trigger.gestureType);
-            }
-            default:
-                throw new RuntimeException("InputGestureData is corrupted, invalid trigger type!");
-
-        }
+        return createTriggerFromAidlTrigger(mInputGestureData.trigger);
     }
 
     /** Returns the action to perform for this input gesture */
@@ -147,18 +127,7 @@
                         "No app launch data for system action launch application");
             }
             AidlInputGestureData data = new AidlInputGestureData();
-            data.trigger = new AidlInputGestureData.Trigger();
-            if (mTrigger instanceof KeyTrigger keyTrigger) {
-                data.trigger.setKey(new AidlInputGestureData.KeyTrigger());
-                data.trigger.getKey().keycode = keyTrigger.getKeycode();
-                data.trigger.getKey().modifierState = keyTrigger.getModifierState();
-            } else if (mTrigger instanceof TouchpadTrigger touchpadTrigger) {
-                data.trigger.setTouchpadGesture(new AidlInputGestureData.TouchpadGestureTrigger());
-                data.trigger.getTouchpadGesture().gestureType =
-                        touchpadTrigger.getTouchpadGestureType();
-            } else {
-                throw new IllegalArgumentException("Invalid trigger type!");
-            }
+            data.trigger = mTrigger.getAidlTrigger();
             data.gestureType = mKeyGestureType;
             if (mAppLaunchData != null) {
                 if (mAppLaunchData instanceof AppLaunchData.CategoryData categoryData) {
@@ -198,6 +167,7 @@
     }
 
     public interface Trigger {
+        AidlInputGestureData.Trigger getAidlTrigger();
     }
 
     /** Creates a input gesture trigger based on a key press */
@@ -210,85 +180,128 @@
         return new TouchpadTrigger(touchpadGestureType);
     }
 
+    public static Trigger createTriggerFromAidlTrigger(AidlInputGestureData.Trigger aidlTrigger) {
+        switch (aidlTrigger.getTag()) {
+            case AidlInputGestureData.Trigger.Tag.key: {
+                AidlInputGestureData.KeyTrigger trigger = aidlTrigger.getKey();
+                if (trigger == null) {
+                    throw new RuntimeException("aidlTrigger is corrupted, null key trigger!");
+                }
+                return new KeyTrigger(trigger);
+            }
+            case AidlInputGestureData.Trigger.Tag.touchpadGesture: {
+                AidlInputGestureData.TouchpadGestureTrigger trigger =
+                        aidlTrigger.getTouchpadGesture();
+                if (trigger == null) {
+                    throw new RuntimeException(
+                            "aidlTrigger is corrupted, null touchpad trigger!");
+                }
+                return new TouchpadTrigger(trigger);
+            }
+            default:
+                throw new RuntimeException("aidlTrigger is corrupted, invalid trigger type!");
+
+        }
+    }
+
     /** Key based input gesture trigger */
     public static class KeyTrigger implements Trigger {
-        private static final int SHORTCUT_META_MASK =
-                KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON
-                        | KeyEvent.META_SHIFT_ON;
-        private final int mKeycode;
-        private final int mModifierState;
+
+        AidlInputGestureData.KeyTrigger mAidlKeyTrigger;
+
+        private KeyTrigger(@NonNull AidlInputGestureData.KeyTrigger aidlKeyTrigger) {
+            mAidlKeyTrigger = aidlKeyTrigger;
+        }
 
         private KeyTrigger(int keycode, int modifierState) {
             if (keycode <= KeyEvent.KEYCODE_UNKNOWN || keycode > KeyEvent.getMaxKeyCode()) {
                 throw new IllegalArgumentException("Invalid keycode = " + keycode);
             }
-            mKeycode = keycode;
-            mModifierState = modifierState;
+            mAidlKeyTrigger = new AidlInputGestureData.KeyTrigger();
+            mAidlKeyTrigger.keycode = keycode;
+            mAidlKeyTrigger.modifierState = modifierState;
         }
 
         public int getKeycode() {
-            return mKeycode;
+            return mAidlKeyTrigger.keycode;
         }
 
         public int getModifierState() {
-            return mModifierState;
+            return mAidlKeyTrigger.modifierState;
+        }
+
+        public AidlInputGestureData.Trigger getAidlTrigger() {
+            AidlInputGestureData.Trigger trigger = new AidlInputGestureData.Trigger();
+            trigger.setKey(mAidlKeyTrigger);
+            return trigger;
         }
 
         @Override
         public boolean equals(Object o) {
             if (this == o) return true;
             if (!(o instanceof KeyTrigger that)) return false;
-            return mKeycode == that.mKeycode && mModifierState == that.mModifierState;
+            return Objects.equals(mAidlKeyTrigger, that.mAidlKeyTrigger);
         }
 
         @Override
         public int hashCode() {
-            return Objects.hash(mKeycode, mModifierState);
+            return mAidlKeyTrigger.hashCode();
         }
 
         @Override
         public String toString() {
             return "KeyTrigger{" +
-                    "mKeycode=" + KeyEvent.keyCodeToString(mKeycode) +
-                    ", mModifierState=" + mModifierState +
+                    "mKeycode=" + KeyEvent.keyCodeToString(mAidlKeyTrigger.keycode) +
+                    ", mModifierState=" + mAidlKeyTrigger.modifierState +
                     '}';
         }
     }
 
     /** Touchpad based input gesture trigger */
     public static class TouchpadTrigger implements Trigger {
-        private final int mTouchpadGestureType;
+        AidlInputGestureData.TouchpadGestureTrigger mAidlTouchpadTrigger;
+
+        private TouchpadTrigger(
+                @NonNull AidlInputGestureData.TouchpadGestureTrigger aidlTouchpadTrigger) {
+            mAidlTouchpadTrigger = aidlTouchpadTrigger;
+        }
 
         private TouchpadTrigger(int touchpadGestureType) {
             if (touchpadGestureType != TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP) {
                 throw new IllegalArgumentException(
                         "Invalid touchpadGestureType = " + touchpadGestureType);
             }
-            mTouchpadGestureType = touchpadGestureType;
+            mAidlTouchpadTrigger = new AidlInputGestureData.TouchpadGestureTrigger();
+            mAidlTouchpadTrigger.gestureType = touchpadGestureType;
         }
 
         public int getTouchpadGestureType() {
-            return mTouchpadGestureType;
+            return mAidlTouchpadTrigger.gestureType;
+        }
+
+        public AidlInputGestureData.Trigger getAidlTrigger() {
+            AidlInputGestureData.Trigger trigger = new AidlInputGestureData.Trigger();
+            trigger.setTouchpadGesture(mAidlTouchpadTrigger);
+            return trigger;
         }
 
         @Override
         public String toString() {
             return "TouchpadTrigger{" +
-                    "mTouchpadGestureType=" + mTouchpadGestureType +
+                    "mTouchpadGestureType=" + mAidlTouchpadTrigger.gestureType +
                     '}';
         }
 
         @Override
         public boolean equals(Object o) {
             if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            TouchpadTrigger that = (TouchpadTrigger) o;
-            return mTouchpadGestureType == that.mTouchpadGestureType;
+            if (!(o instanceof TouchpadTrigger that)) return false;
+            return Objects.equals(mAidlTouchpadTrigger, that.mAidlTouchpadTrigger);
         }
 
         @Override
         public int hashCode() {
-            return Objects.hashCode(mTouchpadGestureType);
+            return mAidlTouchpadTrigger.hashCode();
         }
     }
 
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 1801df0..2a5666c 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -615,6 +615,7 @@
             WAKE_REASON_WAKE_KEY,
             WAKE_REASON_WAKE_MOTION,
             WAKE_REASON_HDMI,
+            WAKE_REASON_LID,
             WAKE_REASON_DISPLAY_GROUP_ADDED,
             WAKE_REASON_DISPLAY_GROUP_TURNED_ON,
             WAKE_REASON_UNFOLD_DEVICE,
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ec58eff..11dddfb 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -13328,6 +13328,16 @@
         public static final String AUTO_TIME_ZONE_EXPLICIT = "auto_time_zone_explicit";
 
         /**
+         * Value to specify if the device should send notifications when {@link #AUTO_TIME_ZONE} is
+         * on and the device's time zone changes.
+         *
+         * <p>1=yes, 0=no.
+         *
+         * @hide
+         */
+        public static final String TIME_ZONE_NOTIFICATIONS = "time_zone_notifications";
+
+        /**
          * URI for the car dock "in" event sound.
          * @hide
          */
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 20e3f6b..2911b0a 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -464,7 +464,12 @@
      * Returns false if the legacy back behavior should be used.
      */
     public boolean isOnBackInvokedCallbackEnabled() {
-        return isOnBackInvokedCallbackEnabled(mChecker.getContext());
+        final Context hostContext = mChecker.getContext();
+        if (hostContext == null) {
+            Log.w(TAG, "OnBackInvokedCallback is disabled, host context is removed!");
+            return false;
+        }
+        return isOnBackInvokedCallbackEnabled(hostContext);
     }
 
     /**
@@ -695,7 +700,12 @@
          */
         public boolean checkApplicationCallbackRegistration(int priority,
                 OnBackInvokedCallback callback) {
-            if (!WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(getContext())
+            final Context hostContext = getContext();
+            if (hostContext == null) {
+                Log.w(TAG, "OnBackInvokedCallback is disabled, host context is removed!");
+                return false;
+            }
+            if (!WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(hostContext)
                     && !(callback instanceof CompatOnBackInvokedCallback)) {
                 Log.w(TAG,
                         "OnBackInvokedCallback is not enabled for the application."
@@ -720,7 +730,7 @@
             return true;
         }
 
-        private Context getContext() {
+        @Nullable private Context getContext() {
             return mContext.get();
         }
     }
diff --git a/core/java/com/android/internal/notification/SystemNotificationChannels.java b/core/java/com/android/internal/notification/SystemNotificationChannels.java
index 5635943..972c2ea 100644
--- a/core/java/com/android/internal/notification/SystemNotificationChannels.java
+++ b/core/java/com/android/internal/notification/SystemNotificationChannels.java
@@ -49,6 +49,7 @@
     public static final String NETWORK_ALERTS = "NETWORK_ALERTS";
     public static final String NETWORK_AVAILABLE = "NETWORK_AVAILABLE";
     public static final String VPN = "VPN";
+    public static final String TIME = "TIME";
     /**
      * @deprecated Legacy device admin channel with low importance which is no longer used,
      *  Use the high importance {@link #DEVICE_ADMIN} channel instead.
@@ -146,6 +147,12 @@
                 NotificationManager.IMPORTANCE_LOW);
         channelsList.add(vpn);
 
+        final NotificationChannel time = new NotificationChannel(
+                TIME,
+                context.getString(R.string.notification_channel_system_time),
+                NotificationManager.IMPORTANCE_DEFAULT);
+        channelsList.add(time);
+
         final NotificationChannel deviceAdmin = new NotificationChannel(
                 DEVICE_ADMIN,
                 getDeviceAdminNotificationChannelName(context),
diff --git a/core/res/res/layout/notification_2025_conversation_header.xml b/core/res/res/layout/notification_2025_conversation_header.xml
index db79e79..1bde173 100644
--- a/core/res/res/layout/notification_2025_conversation_header.xml
+++ b/core/res/res/layout/notification_2025_conversation_header.xml
@@ -136,10 +136,10 @@
 
     <ImageView
         android:id="@+id/phishing_alert"
-        android:layout_width="@dimen/notification_phishing_alert_size"
-        android:layout_height="@dimen/notification_phishing_alert_size"
-        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
-        android:baseline="10dp"
+        android:layout_width="@dimen/notification_2025_badge_size"
+        android:layout_height="@dimen/notification_2025_badge_size"
+        android:layout_marginStart="@dimen/notification_2025_badge_margin"
+        android:baseline="@dimen/notification_2025_badge_baseline"
         android:scaleType="fitCenter"
         android:src="@drawable/ic_dialog_alert_material"
         android:visibility="gone"
@@ -148,10 +148,10 @@
 
     <ImageView
         android:id="@+id/profile_badge"
-        android:layout_width="@dimen/notification_badge_size"
-        android:layout_height="@dimen/notification_badge_size"
-        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
-        android:baseline="10dp"
+        android:layout_width="@dimen/notification_2025_badge_size"
+        android:layout_height="@dimen/notification_2025_badge_size"
+        android:layout_marginStart="@dimen/notification_2025_badge_margin"
+        android:baseline="@dimen/notification_2025_badge_baseline"
         android:scaleType="fitCenter"
         android:visibility="gone"
         android:contentDescription="@string/notification_work_profile_content_description"
@@ -159,10 +159,10 @@
 
     <ImageView
         android:id="@+id/alerted_icon"
-        android:layout_width="@dimen/notification_alerted_size"
-        android:layout_height="@dimen/notification_alerted_size"
-        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
-        android:baseline="10dp"
+        android:layout_width="@dimen/notification_2025_badge_size"
+        android:layout_height="@dimen/notification_2025_badge_size"
+        android:layout_marginStart="@dimen/notification_2025_badge_margin"
+        android:baseline="@dimen/notification_2025_badge_baseline"
         android:contentDescription="@string/notification_alerted_content_description"
         android:scaleType="fitCenter"
         android:src="@drawable/ic_notifications_alerted"
diff --git a/core/res/res/layout/notification_2025_template_collapsed_base.xml b/core/res/res/layout/notification_2025_template_collapsed_base.xml
index f108ce5..d29b7af 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_base.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_base.xml
@@ -87,7 +87,7 @@
                 >
 
                 <!--
-                NOTE: The notification_top_line_views layout contains the app_name_text.
+                NOTE: The notification_2025_top_line_views layout contains the app_name_text.
                 In order to include the title view at the beginning, the Notification.Builder
                 has logic to hide that view whenever this title view is to be visible.
                 -->
@@ -104,7 +104,7 @@
                     android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
                     />
 
-                <include layout="@layout/notification_top_line_views" />
+                <include layout="@layout/notification_2025_top_line_views" />
 
             </NotificationTopLineView>
 
diff --git a/core/res/res/layout/notification_2025_template_collapsed_media.xml b/core/res/res/layout/notification_2025_template_collapsed_media.xml
index bd17a3a..5beab50 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_media.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_media.xml
@@ -89,7 +89,7 @@
                 >
 
                 <!--
-                NOTE: The notification_top_line_views layout contains the app_name_text.
+                NOTE: The notification_2025_top_line_views layout contains the app_name_text.
                 In order to include the title view at the beginning, the Notification.Builder
                 has logic to hide that view whenever this title view is to be visible.
                 -->
@@ -106,7 +106,7 @@
                     android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
                     />
 
-                <include layout="@layout/notification_top_line_views" />
+                <include layout="@layout/notification_2025_top_line_views" />
 
             </NotificationTopLineView>
 
diff --git a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
index edbebb1..d7c3263 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
@@ -115,7 +115,7 @@
                         >
 
                         <!--
-                        NOTE: The notification_top_line_views layout contains the app_name_text.
+                        NOTE: The notification_2025_top_line_views layout contains the app_name_text.
                         In order to include the title view at the beginning, the Notification.Builder
                         has logic to hide that view whenever this title view is to be visible.
                         -->
@@ -132,7 +132,7 @@
                             android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
                             />
 
-                        <include layout="@layout/notification_top_line_views" />
+                        <include layout="@layout/notification_2025_top_line_views" />
 
                     </NotificationTopLineView>
 
diff --git a/core/res/res/layout/notification_2025_template_header.xml b/core/res/res/layout/notification_2025_template_header.xml
index 0c07053..72b3798 100644
--- a/core/res/res/layout/notification_2025_template_header.xml
+++ b/core/res/res/layout/notification_2025_template_header.xml
@@ -68,7 +68,7 @@
         android:theme="@style/Theme.DeviceDefault.Notification"
         >
 
-        <include layout="@layout/notification_top_line_views" />
+        <include layout="@layout/notification_2025_top_line_views" />
 
     </NotificationTopLineView>
 
diff --git a/core/res/res/layout/notification_2025_template_heads_up_base.xml b/core/res/res/layout/notification_2025_template_heads_up_base.xml
index e4ff835..084ec7d 100644
--- a/core/res/res/layout/notification_2025_template_heads_up_base.xml
+++ b/core/res/res/layout/notification_2025_template_heads_up_base.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?><!--
-  ~ Copyright (C) 2014 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.
diff --git a/core/res/res/layout/notification_2025_top_line_views.xml b/core/res/res/layout/notification_2025_top_line_views.xml
new file mode 100644
index 0000000..7487346
--- /dev/null
+++ b/core/res/res/layout/notification_2025_top_line_views.xml
@@ -0,0 +1,159 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2025 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
+  -->
+<!--
+ This layout file should be included inside a NotificationTopLineView, sometimes after a
+ <TextView android:id="@+id/title"/>
+-->
+<merge
+    xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <TextView
+        android:id="@+id/app_name_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@dimen/notification_header_separating_margin"
+        android:singleLine="true"
+        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
+        android:visibility="?attr/notificationHeaderAppNameVisibility"
+        />
+
+    <TextView
+        android:id="@+id/header_text_secondary_divider"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
+        android:layout_marginStart="@dimen/notification_header_separating_margin"
+        android:layout_marginEnd="@dimen/notification_header_separating_margin"
+        android:text="@string/notification_header_divider_symbol"
+        android:visibility="gone"
+        />
+
+    <TextView
+        android:id="@+id/header_text_secondary"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
+        android:layout_marginStart="@dimen/notification_header_separating_margin"
+        android:layout_marginEnd="@dimen/notification_header_separating_margin"
+        android:visibility="gone"
+        android:singleLine="true"
+        />
+
+    <TextView
+        android:id="@+id/header_text_divider"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
+        android:layout_marginStart="@dimen/notification_header_separating_margin"
+        android:layout_marginEnd="@dimen/notification_header_separating_margin"
+        android:text="@string/notification_header_divider_symbol"
+        android:visibility="gone"
+        />
+
+    <TextView
+        android:id="@+id/header_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
+        android:layout_marginStart="@dimen/notification_header_separating_margin"
+        android:layout_marginEnd="@dimen/notification_header_separating_margin"
+        android:visibility="gone"
+        android:singleLine="true"
+        />
+
+    <TextView
+        android:id="@+id/time_divider"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
+        android:layout_marginStart="@dimen/notification_header_separating_margin"
+        android:layout_marginEnd="@dimen/notification_header_separating_margin"
+        android:text="@string/notification_header_divider_symbol"
+        android:singleLine="true"
+        android:visibility="gone"
+        />
+
+    <DateTimeView
+        android:id="@+id/time"
+        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Time"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/notification_header_separating_margin"
+        android:layout_marginEnd="@dimen/notification_header_separating_margin"
+        android:showRelative="true"
+        android:singleLine="true"
+        android:visibility="gone"
+        />
+
+    <ViewStub
+        android:id="@+id/chronometer"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/notification_header_separating_margin"
+        android:layout_marginEnd="@dimen/notification_header_separating_margin"
+        android:layout="@layout/notification_template_part_chronometer"
+        android:visibility="gone"
+        />
+
+    <ImageButton
+        android:id="@+id/feedback"
+        android:layout_width="@dimen/notification_feedback_size"
+        android:layout_height="@dimen/notification_feedback_size"
+        android:layout_marginStart="@dimen/notification_header_separating_margin"
+        android:baseline="13dp"
+        android:scaleType="fitCenter"
+        android:src="@drawable/ic_feedback_indicator"
+        android:background="?android:selectableItemBackgroundBorderless"
+        android:visibility="gone"
+        android:contentDescription="@string/notification_feedback_indicator"
+        />
+
+    <ImageView
+        android:id="@+id/phishing_alert"
+        android:layout_width="@dimen/notification_2025_badge_size"
+        android:layout_height="@dimen/notification_2025_badge_size"
+        android:layout_marginStart="@dimen/notification_2025_badge_margin"
+        android:baseline="@dimen/notification_2025_badge_baseline"
+        android:scaleType="fitCenter"
+        android:src="@drawable/ic_dialog_alert_material"
+        android:visibility="gone"
+        android:contentDescription="@string/notification_phishing_alert_content_description"
+        />
+
+    <ImageView
+        android:id="@+id/profile_badge"
+        android:layout_width="@dimen/notification_2025_badge_size"
+        android:layout_height="@dimen/notification_2025_badge_size"
+        android:layout_marginStart="@dimen/notification_2025_badge_margin"
+        android:baseline="@dimen/notification_2025_badge_baseline"
+        android:scaleType="fitCenter"
+        android:visibility="gone"
+        android:contentDescription="@string/notification_work_profile_content_description"
+        />
+
+    <ImageView
+        android:id="@+id/alerted_icon"
+        android:layout_width="@dimen/notification_2025_badge_size"
+        android:layout_height="@dimen/notification_2025_badge_size"
+        android:layout_marginStart="@dimen/notification_2025_badge_margin"
+        android:baseline="@dimen/notification_2025_badge_baseline"
+        android:contentDescription="@string/notification_alerted_content_description"
+        android:scaleType="fitCenter"
+        android:src="@drawable/ic_notifications_alerted"
+        android:visibility="gone"
+        />
+</merge>
+
diff --git a/core/res/res/values-watch/config.xml b/core/res/res/values-watch/config.xml
index e6295ea..4ff3f88 100644
--- a/core/res/res/values-watch/config.xml
+++ b/core/res/res/values-watch/config.xml
@@ -101,6 +101,13 @@
          P.S this is a change only intended for wear devices. -->
     <bool name="config_enableViewGroupScalingFading">true</bool>
 
-    <!-- Allow the gesture to double tap the power button to trigger a target action. -->
-    <bool name="config_doubleTapPowerGestureEnabled">false</bool>
+    <!-- Controls the double tap power button gesture to trigger a target action.
+         0: Gesture is disabled
+         1: Launch camera mode, allowing the user to disable/enable the double tap power gesture
+            from launching the camera application.
+         2: Multi target mode, allowing the user to select one of the targets defined in
+            config_doubleTapPowerGestureMultiTargetDefaultAction and to disable/enable the double
+            tap power gesture from triggering the selected target action.
+    -->
+    <integer name="config_doubleTapPowerGestureMode">0</integer>
 </resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index c50c5e9..ce9a0c6 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2083,6 +2083,18 @@
          See com.android.server.timezonedetector.TimeZoneDetectorStrategy for more information. -->
     <bool name="config_supportTelephonyTimeZoneFallback" translatable="false">true</bool>
 
+    <!-- Whether the time notifications feature is enabled. Settings this to false means the feature
+         cannot be used. Setting this to true means the feature can be enabled on the device. -->
+    <bool name="config_enableTimeZoneNotificationsSupported" translatable="false">true</bool>
+
+    <!-- Whether the time zone notifications tracking feature is enabled. Settings this to false
+         means the feature cannot be used. -->
+    <bool name="config_enableTimeZoneNotificationsTrackingSupported" translatable="false">true</bool>
+
+    <!-- Whether the time zone manual change tracking feature is enabled. Settings this to false
+         means the feature cannot be used. -->
+    <bool name="config_enableTimeZoneManualChangeTrackingSupported" translatable="false">true</bool>
+
     <!-- Whether to enable network location overlay which allows network location provider to be
          replaced by an app at run-time. When disabled, only the
          config_networkLocationProviderPackageName package will be searched for network location
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 2adb791..4f7351c 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -608,12 +608,23 @@
     <!-- Size of the feedback indicator for notifications -->
     <dimen name="notification_feedback_size">20dp</dimen>
 
+    <!-- Size of the (work) profile badge for notifications -->
+    <dimen name="notification_badge_size">12dp</dimen>
+
+    <!-- Size of the (work) profile badge for notifications (2025 redesign version).
+         Scales with font size. Chosen to look good alongside notification_subtext_size text. -->
+    <dimen name="notification_2025_badge_size">14sp</dimen>
+
+    <!-- Baseline for aligning icons in the top line (like the work profile icon or alerting icon)
+         to the text properly. This is equal to notification_2025_badge_size - 2sp. -->
+    <dimen name="notification_2025_badge_baseline">12sp</dimen>
+
+    <!-- Spacing for the top line icons (e.g. the work profile badge). -->
+    <dimen name="notification_2025_badge_margin">4dp</dimen>
+
     <!-- Size of the phishing alert for notifications -->
     <dimen name="notification_phishing_alert_size">@dimen/notification_badge_size</dimen>
 
-    <!-- Size of the profile badge for notifications -->
-    <dimen name="notification_badge_size">12dp</dimen>
-
     <!-- Size of the alerted icon for notifications -->
     <dimen name="notification_alerted_size">@dimen/notification_badge_size</dimen>
 
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index ad9e725..debc5e9 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -853,6 +853,9 @@
     <!-- Text shown when viewing channel settings for notifications related to vpn status -->
     <string name="notification_channel_vpn">VPN status</string>
 
+    <!-- Text shown when viewing channel settings for notifications related to system time -->
+    <string name="notification_channel_system_time">Time and time zones</string>
+
     <!-- Notification channel name. This channel sends high-priority alerts from the user's IT admin for key updates about the user's work device or work profile. -->
     <string name="notification_channel_device_admin">Alerts from your IT admin</string>
 
@@ -3879,6 +3882,12 @@
     <string name="carrier_app_notification_title">New SIM inserted</string>
     <string name="carrier_app_notification_text">Tap to set it up</string>
 
+    <!-- Time zone notification strings -->
+    <!-- Title for time zone change notifications -->
+    <string name="time_zone_change_notification_title">Your time zone changed</string>
+    <!-- Body for time zone change notifications -->
+    <string name="time_zone_change_notification_body">You\'re now in <xliff:g id="time_zone_display_name">%1$s</xliff:g> (<xliff:g id="time_zone_offset">%2$s</xliff:g>)</string>
+
     <!-- Date/Time picker dialogs strings -->
 
     <!-- The title of the time picker dialog. [CHAR LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d20b95f..f89ca44 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -575,6 +575,7 @@
   <java-symbol type="dimen" name="notification_top_pad_large_text" />
   <java-symbol type="dimen" name="notification_top_pad_large_text_narrow" />
   <java-symbol type="dimen" name="notification_badge_size" />
+  <java-symbol type="dimen" name="notification_2025_badge_size" />
   <java-symbol type="dimen" name="immersive_mode_cling_width" />
   <java-symbol type="dimen" name="accessibility_magnification_indicator_width" />
   <java-symbol type="dimen" name="circular_display_mask_thickness" />
@@ -2377,6 +2378,9 @@
   <java-symbol type="string" name="config_secondaryLocationTimeZoneProviderPackageName" />
   <java-symbol type="bool" name="config_enableTelephonyTimeZoneDetection" />
   <java-symbol type="bool" name="config_supportTelephonyTimeZoneFallback" />
+  <java-symbol type="bool" name="config_enableTimeZoneNotificationsSupported" />
+  <java-symbol type="bool" name="config_enableTimeZoneNotificationsTrackingSupported" />
+  <java-symbol type="bool" name="config_enableTimeZoneManualChangeTrackingSupported" />
   <java-symbol type="bool" name="config_autoResetAirplaneMode" />
   <java-symbol type="string" name="config_notificationAccessConfirmationActivity" />
   <java-symbol type="bool" name="config_preventImeStartupUnlessTextEditor" />
@@ -4025,6 +4029,7 @@
   <java-symbol type="string" name="notification_channel_network_available" />
   <java-symbol type="array" name="config_defaultCloudSearchServices" />
   <java-symbol type="string" name="notification_channel_vpn" />
+  <java-symbol type="string" name="notification_channel_system_time" />
   <java-symbol type="string" name="notification_channel_device_admin" />
   <java-symbol type="string" name="notification_channel_alerts" />
   <java-symbol type="string" name="notification_channel_retail_mode" />
@@ -4035,6 +4040,8 @@
   <java-symbol type="string" name="notification_channel_accessibility_hearing_device" />
   <java-symbol type="string" name="notification_channel_accessibility_security_policy" />
   <java-symbol type="string" name="notification_channel_display" />
+  <java-symbol type="string" name="time_zone_change_notification_title" />
+  <java-symbol type="string" name="time_zone_change_notification_body" />
   <java-symbol type="string" name="config_defaultAutofillService" />
   <java-symbol type="string" name="config_defaultFieldClassificationService" />
   <java-symbol type="string" name="config_defaultOnDeviceSpeechRecognitionService" />
diff --git a/core/tests/coretests/src/android/app/NotificationManagerTest.java b/core/tests/coretests/src/android/app/NotificationManagerTest.java
index 6538ce8..3d6e122 100644
--- a/core/tests/coretests/src/android/app/NotificationManagerTest.java
+++ b/core/tests/coretests/src/android/app/NotificationManagerTest.java
@@ -16,6 +16,8 @@
 
 package android.app;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
@@ -25,8 +27,12 @@
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.ParceledListSlice;
+import android.os.UserHandle;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.flag.junit.SetFlagsRule;
@@ -35,6 +41,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -42,6 +49,7 @@
 
 import java.time.Instant;
 import java.time.InstantSource;
+import java.util.List;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
@@ -50,14 +58,24 @@
     @Rule
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
-    private Context mContext;
     private NotificationManagerWithMockService mNotificationManager;
     private final FakeClock mClock = new FakeClock();
 
+    private PackageTestableContext mContext;
+
     @Before
     public void setUp() {
-        mContext = ApplicationProvider.getApplicationContext();
+        mContext = new PackageTestableContext(ApplicationProvider.getApplicationContext());
         mNotificationManager = new NotificationManagerWithMockService(mContext, mClock);
+
+        // Caches must be in test mode in order to be used in tests.
+        PropertyInvalidatedCache.setTestMode(true);
+        mNotificationManager.setChannelCacheToTestMode();
+    }
+
+    @After
+    public void tearDown() {
+        PropertyInvalidatedCache.setTestMode(false);
     }
 
     @Test
@@ -243,12 +261,161 @@
                 anyInt(), any(), anyInt());
     }
 
+    @Test
+    @EnableFlags(Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+    public void getNotificationChannel_cachedUntilInvalidated() throws Exception {
+        // Invalidate the cache first because the cache won't do anything until then
+        NotificationManager.invalidateNotificationChannelCache();
+
+        // It doesn't matter what the returned contents are, as long as we return a channel.
+        // This setup must set up getNotificationChannels(), as that's the method called.
+        when(mNotificationManager.mBackendService.getNotificationChannels(any(), any(),
+                anyInt())).thenReturn(new ParceledListSlice<>(List.of(exampleChannel())));
+
+        // ask for the same channel 100 times without invalidating the cache
+        for (int i = 0; i < 100; i++) {
+            NotificationChannel unused = mNotificationManager.getNotificationChannel("id");
+        }
+
+        // invalidate the cache; then ask again
+        NotificationManager.invalidateNotificationChannelCache();
+        NotificationChannel unused = mNotificationManager.getNotificationChannel("id");
+
+        verify(mNotificationManager.mBackendService, times(2))
+                .getNotificationChannels(any(), any(), anyInt());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+    public void getNotificationChannel_sameApp_oneCall() throws Exception {
+        NotificationManager.invalidateNotificationChannelCache();
+
+        NotificationChannel c1 = new NotificationChannel("id1", "name1",
+                NotificationManager.IMPORTANCE_DEFAULT);
+        NotificationChannel c2 = new NotificationChannel("id2", "name2",
+                NotificationManager.IMPORTANCE_NONE);
+
+        when(mNotificationManager.mBackendService.getNotificationChannels(any(), any(),
+                anyInt())).thenReturn(new ParceledListSlice<>(List.of(c1, c2)));
+
+        assertThat(mNotificationManager.getNotificationChannel("id1")).isEqualTo(c1);
+        assertThat(mNotificationManager.getNotificationChannel("id2")).isEqualTo(c2);
+        assertThat(mNotificationManager.getNotificationChannel("id3")).isNull();
+
+        verify(mNotificationManager.mBackendService, times(1))
+                .getNotificationChannels(any(), any(), anyInt());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+    public void getNotificationChannels_cachedUntilInvalidated() throws Exception {
+        NotificationManager.invalidateNotificationChannelCache();
+        when(mNotificationManager.mBackendService.getNotificationChannels(any(), any(),
+                anyInt())).thenReturn(new ParceledListSlice<>(List.of(exampleChannel())));
+
+        // ask for channels 100 times without invalidating the cache
+        for (int i = 0; i < 100; i++) {
+            List<NotificationChannel> unused = mNotificationManager.getNotificationChannels();
+        }
+
+        // invalidate the cache; then ask again
+        NotificationManager.invalidateNotificationChannelCache();
+        List<NotificationChannel> res = mNotificationManager.getNotificationChannels();
+
+        verify(mNotificationManager.mBackendService, times(2))
+                .getNotificationChannels(any(), any(), anyInt());
+        assertThat(res).containsExactlyElementsIn(List.of(exampleChannel()));
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+    public void getNotificationChannel_channelAndConversationLookup() throws Exception {
+        NotificationManager.invalidateNotificationChannelCache();
+
+        // Full list of channels: c1; conv1 = child of c1; c2 is unrelated
+        NotificationChannel c1 = new NotificationChannel("id", "name",
+                NotificationManager.IMPORTANCE_DEFAULT);
+        NotificationChannel conv1 = new NotificationChannel("", "name_conversation",
+                NotificationManager.IMPORTANCE_DEFAULT);
+        conv1.setConversationId("id", "id_conversation");
+        NotificationChannel c2 = new NotificationChannel("other", "name2",
+                NotificationManager.IMPORTANCE_DEFAULT);
+
+        when(mNotificationManager.mBackendService.getNotificationChannels(any(), any(), anyInt()))
+                .thenReturn(new ParceledListSlice<>(List.of(c1, conv1, c2)));
+
+        // Lookup for channel c1 and c2: returned as expected
+        assertThat(mNotificationManager.getNotificationChannel("id")).isEqualTo(c1);
+        assertThat(mNotificationManager.getNotificationChannel("other")).isEqualTo(c2);
+
+        // Lookup for conv1 should return conv1
+        assertThat(mNotificationManager.getNotificationChannel("id", "id_conversation")).isEqualTo(
+                conv1);
+
+        // Lookup for a different conversation channel that doesn't exist, whose parent channel id
+        // is "id", should return c1
+        assertThat(mNotificationManager.getNotificationChannel("id", "nonexistent")).isEqualTo(c1);
+
+        // Lookup of a nonexistent channel is null
+        assertThat(mNotificationManager.getNotificationChannel("id3")).isNull();
+
+        // All of that should have been one call to getNotificationChannels()
+        verify(mNotificationManager.mBackendService, times(1))
+                .getNotificationChannels(any(), any(), anyInt());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+    public void getNotificationChannel_differentPackages() throws Exception {
+        NotificationManager.invalidateNotificationChannelCache();
+        final String pkg1 = "one";
+        final String pkg2 = "two";
+        final int userId = 0;
+        final int userId1 = 1;
+
+        // multiple channels with the same ID, but belonging to different packages/users
+        NotificationChannel channel1 = new NotificationChannel("id", "name1",
+                NotificationManager.IMPORTANCE_DEFAULT);
+        NotificationChannel channel2 = channel1.copy();
+        channel2.setName("name2");
+        NotificationChannel channel3 = channel1.copy();
+        channel3.setName("name3");
+
+        when(mNotificationManager.mBackendService.getNotificationChannels(any(), eq(pkg1),
+                eq(userId))).thenReturn(new ParceledListSlice<>(List.of(channel1)));
+        when(mNotificationManager.mBackendService.getNotificationChannels(any(), eq(pkg2),
+                eq(userId))).thenReturn(new ParceledListSlice<>(List.of(channel2)));
+        when(mNotificationManager.mBackendService.getNotificationChannels(any(), eq(pkg1),
+                eq(userId1))).thenReturn(new ParceledListSlice<>(List.of(channel3)));
+
+        // set our context to pretend to be from package 1 and userId 0
+        mContext.setParameters(pkg1, pkg1, userId);
+        assertThat(mNotificationManager.getNotificationChannel("id")).isEqualTo(channel1);
+
+        // now package 2
+        mContext.setParameters(pkg2, pkg2, userId);
+        assertThat(mNotificationManager.getNotificationChannel("id")).isEqualTo(channel2);
+
+        // now pkg1 for a different user
+        mContext.setParameters(pkg1, pkg1, userId1);
+        assertThat(mNotificationManager.getNotificationChannel("id")).isEqualTo(channel3);
+
+        // Those should have been three different calls
+        verify(mNotificationManager.mBackendService, times(3))
+                .getNotificationChannels(any(), any(), anyInt());
+    }
+
     private Notification exampleNotification() {
         return new Notification.Builder(mContext, "channel")
                 .setSmallIcon(android.R.drawable.star_big_on)
                 .build();
     }
 
+    private NotificationChannel exampleChannel() {
+        return new NotificationChannel("id", "channel_name",
+                NotificationManager.IMPORTANCE_DEFAULT);
+    }
+
     private static class NotificationManagerWithMockService extends NotificationManager {
 
         private final INotificationManager mBackendService;
@@ -264,6 +431,48 @@
         }
     }
 
+    // Helper context wrapper class where we can control just the return values of getPackageName,
+    // getOpPackageName, and getUserId (used in getNotificationChannels).
+    private static class PackageTestableContext extends ContextWrapper {
+        private String mPackage;
+        private String mOpPackage;
+        private Integer mUserId;
+
+        PackageTestableContext(Context base) {
+            super(base);
+        }
+
+        void setParameters(String packageName, String opPackageName, int userId) {
+            mPackage = packageName;
+            mOpPackage = opPackageName;
+            mUserId = userId;
+        }
+
+        @Override
+        public String getPackageName() {
+            if (mPackage != null) return mPackage;
+            return super.getPackageName();
+        }
+
+        @Override
+        public String getOpPackageName() {
+            if (mOpPackage != null) return mOpPackage;
+            return super.getOpPackageName();
+        }
+
+        @Override
+        public int getUserId() {
+            if (mUserId != null) return mUserId;
+            return super.getUserId();
+        }
+
+        @Override
+        public UserHandle getUser() {
+            if (mUserId != null) return UserHandle.of(mUserId);
+            return super.getUser();
+        }
+    }
+
     private static class FakeClock implements InstantSource {
 
         private long mNowMillis = 441644400000L;
diff --git a/core/tests/timetests/src/android/app/time/TimeZoneCapabilitiesTest.java b/core/tests/timetests/src/android/app/time/TimeZoneCapabilitiesTest.java
index e368d28..cb8b5ce 100644
--- a/core/tests/timetests/src/android/app/time/TimeZoneCapabilitiesTest.java
+++ b/core/tests/timetests/src/android/app/time/TimeZoneCapabilitiesTest.java
@@ -48,12 +48,14 @@
                 .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
                 .setUseLocationEnabled(true)
                 .setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
-                .setSetManualTimeZoneCapability(CAPABILITY_POSSESSED);
+                .setSetManualTimeZoneCapability(CAPABILITY_POSSESSED)
+                .setConfigureNotificationsEnabledCapability(CAPABILITY_POSSESSED);
         TimeZoneCapabilities.Builder builder2 = new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
                 .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
                 .setUseLocationEnabled(true)
                 .setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
-                .setSetManualTimeZoneCapability(CAPABILITY_POSSESSED);
+                .setSetManualTimeZoneCapability(CAPABILITY_POSSESSED)
+                .setConfigureNotificationsEnabledCapability(CAPABILITY_POSSESSED);
         {
             TimeZoneCapabilities one = builder1.build();
             TimeZoneCapabilities two = builder2.build();
@@ -115,6 +117,13 @@
             TimeZoneCapabilities two = builder2.build();
             assertEquals(one, two);
         }
+
+        builder1.setConfigureNotificationsEnabledCapability(CAPABILITY_NOT_ALLOWED);
+        {
+            TimeZoneCapabilities one = builder1.build();
+            TimeZoneCapabilities two = builder2.build();
+            assertNotEquals(one, two);
+        }
     }
 
     @Test
@@ -123,7 +132,8 @@
                 .setConfigureAutoDetectionEnabledCapability(CAPABILITY_POSSESSED)
                 .setUseLocationEnabled(true)
                 .setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
-                .setSetManualTimeZoneCapability(CAPABILITY_POSSESSED);
+                .setSetManualTimeZoneCapability(CAPABILITY_POSSESSED)
+                .setConfigureNotificationsEnabledCapability(CAPABILITY_POSSESSED);
         assertRoundTripParcelable(builder.build());
 
         builder.setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED);
@@ -137,6 +147,9 @@
 
         builder.setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED);
         assertRoundTripParcelable(builder.build());
+
+        builder.setConfigureNotificationsEnabledCapability(CAPABILITY_NOT_ALLOWED);
+        assertRoundTripParcelable(builder.build());
     }
 
     @Test
@@ -151,6 +164,7 @@
                 .setUseLocationEnabled(true)
                 .setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
                 .setSetManualTimeZoneCapability(CAPABILITY_POSSESSED)
+                .setConfigureNotificationsEnabledCapability(CAPABILITY_POSSESSED)
                 .build();
 
         TimeZoneConfiguration configChange = new TimeZoneConfiguration.Builder()
@@ -175,6 +189,7 @@
                 .setUseLocationEnabled(true)
                 .setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
                 .setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
+                .setConfigureNotificationsEnabledCapability(CAPABILITY_NOT_ALLOWED)
                 .build();
 
         TimeZoneConfiguration configChange = new TimeZoneConfiguration.Builder()
@@ -191,6 +206,7 @@
                 .setUseLocationEnabled(true)
                 .setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
                 .setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
+                .setConfigureNotificationsEnabledCapability(CAPABILITY_NOT_ALLOWED)
                 .build();
 
         {
@@ -204,6 +220,7 @@
                             .setUseLocationEnabled(true)
                             .setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
                             .setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
+                            .setConfigureNotificationsEnabledCapability(CAPABILITY_NOT_ALLOWED)
                             .build();
 
             assertThat(updatedCapabilities).isEqualTo(expectedCapabilities);
@@ -221,6 +238,7 @@
                             .setUseLocationEnabled(false)
                             .setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
                             .setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
+                            .setConfigureNotificationsEnabledCapability(CAPABILITY_NOT_ALLOWED)
                             .build();
 
             assertThat(updatedCapabilities).isEqualTo(expectedCapabilities);
@@ -238,6 +256,7 @@
                             .setUseLocationEnabled(true)
                             .setConfigureGeoDetectionEnabledCapability(CAPABILITY_POSSESSED)
                             .setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
+                            .setConfigureNotificationsEnabledCapability(CAPABILITY_NOT_ALLOWED)
                             .build();
 
             assertThat(updatedCapabilities).isEqualTo(expectedCapabilities);
@@ -255,6 +274,25 @@
                             .setUseLocationEnabled(true)
                             .setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
                             .setSetManualTimeZoneCapability(CAPABILITY_POSSESSED)
+                            .setConfigureNotificationsEnabledCapability(CAPABILITY_NOT_ALLOWED)
+                            .build();
+
+            assertThat(updatedCapabilities).isEqualTo(expectedCapabilities);
+        }
+
+        {
+            TimeZoneCapabilities updatedCapabilities =
+                    new TimeZoneCapabilities.Builder(capabilities)
+                            .setConfigureNotificationsEnabledCapability(CAPABILITY_POSSESSED)
+                            .build();
+
+            TimeZoneCapabilities expectedCapabilities =
+                    new TimeZoneCapabilities.Builder(TEST_USER_HANDLE)
+                            .setConfigureAutoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
+                            .setUseLocationEnabled(true)
+                            .setConfigureGeoDetectionEnabledCapability(CAPABILITY_NOT_ALLOWED)
+                            .setSetManualTimeZoneCapability(CAPABILITY_NOT_ALLOWED)
+                            .setConfigureNotificationsEnabledCapability(CAPABILITY_POSSESSED)
                             .build();
 
             assertThat(updatedCapabilities).isEqualTo(expectedCapabilities);
diff --git a/core/tests/timetests/src/android/app/time/TimeZoneConfigurationTest.java b/core/tests/timetests/src/android/app/time/TimeZoneConfigurationTest.java
index 4ad3e41..345e912 100644
--- a/core/tests/timetests/src/android/app/time/TimeZoneConfigurationTest.java
+++ b/core/tests/timetests/src/android/app/time/TimeZoneConfigurationTest.java
@@ -43,9 +43,11 @@
         TimeZoneConfiguration completeConfig = new TimeZoneConfiguration.Builder()
                 .setAutoDetectionEnabled(true)
                 .setGeoDetectionEnabled(true)
+                .setNotificationsEnabled(true)
                 .build();
         assertTrue(completeConfig.isComplete());
         assertTrue(completeConfig.hasIsGeoDetectionEnabled());
+        assertTrue(completeConfig.hasIsNotificationsEnabled());
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
index 4569cf3..b9fccc1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
@@ -106,11 +106,12 @@
 
     private Runnable mFinishedCallback;
     private RemoteAnimationTarget[] mApps;
-    private IRemoteAnimationFinishedCallback mRemoteCallback;
+    private RemoteAnimationFinishedStub mRemoteCallback;
 
     private static class RemoteAnimationFinishedStub extends IRemoteAnimationFinishedCallback.Stub {
         //the binder callback should not hold strong reference to it to avoid memory leak.
-        private WeakReference<BackAnimationRunner> mRunnerRef;
+        private final WeakReference<BackAnimationRunner> mRunnerRef;
+        private boolean mAbandoned;
 
         private RemoteAnimationFinishedStub(BackAnimationRunner runner) {
             mRunnerRef = new WeakReference<>(runner);
@@ -118,23 +119,29 @@
 
         @Override
         public void onAnimationFinished() {
-            BackAnimationRunner runner = mRunnerRef.get();
+            synchronized (this) {
+                if (mAbandoned) {
+                    return;
+                }
+            }
+            final BackAnimationRunner runner = mRunnerRef.get();
             if (runner == null) {
                 return;
             }
-            if (runner.shouldMonitorCUJ(runner.mApps)) {
-                InteractionJankMonitor.getInstance().end(runner.mCujType);
-            }
+            runner.onAnimationFinish(this);
+        }
 
-            runner.mFinishedCallback.run();
-            for (int i = runner.mApps.length - 1; i >= 0; --i) {
-                 SurfaceControl sc = runner.mApps[i].leash;
-                 if (sc != null && sc.isValid()) {
-                     sc.release();
-                 }
+        void abandon() {
+            synchronized (this) {
+                mAbandoned = true;
+                final BackAnimationRunner runner = mRunnerRef.get();
+                if (runner == null) {
+                    return;
+                }
+                if (runner.shouldMonitorCUJ(runner.mApps)) {
+                    InteractionJankMonitor.getInstance().end(runner.mCujType);
+                }
             }
-            runner.mApps = null;
-            runner.mFinishedCallback = null;
         }
     }
 
@@ -144,13 +151,16 @@
      */
     void startAnimation(RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
             RemoteAnimationTarget[] nonApps, Runnable finishedCallback) {
-        InteractionJankMonitor interactionJankMonitor = InteractionJankMonitor.getInstance();
+        if (mRemoteCallback != null) {
+            mRemoteCallback.abandon();
+            mRemoteCallback = null;
+        }
+        mRemoteCallback = new RemoteAnimationFinishedStub(this);
         mFinishedCallback = finishedCallback;
         mApps = apps;
-        if (mRemoteCallback == null) mRemoteCallback = new RemoteAnimationFinishedStub(this);
         mWaitingAnimation = false;
         if (shouldMonitorCUJ(apps)) {
-            interactionJankMonitor.begin(
+            InteractionJankMonitor.getInstance().begin(
                     apps[0].leash, mContext, mHandler, mCujType);
         }
         try {
@@ -161,6 +171,28 @@
         }
     }
 
+    void onAnimationFinish(RemoteAnimationFinishedStub finished) {
+        mHandler.post(() -> {
+            if (mRemoteCallback != null && finished != mRemoteCallback) {
+                return;
+            }
+            if (shouldMonitorCUJ(mApps)) {
+                InteractionJankMonitor.getInstance().end(mCujType);
+            }
+
+            mFinishedCallback.run();
+            for (int i = mApps.length - 1; i >= 0; --i) {
+                final SurfaceControl sc = mApps[i].leash;
+                if (sc != null && sc.isValid()) {
+                    sc.release();
+                }
+            }
+            mApps = null;
+            mFinishedCallback = null;
+            mRemoteCallback = null;
+        });
+    }
+
     @VisibleForTesting
     boolean shouldMonitorCUJ(RemoteAnimationTarget[] apps) {
         return apps.length > 0 && mCujType != NO_CUJ;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index b796b41..1323fe0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -20,7 +20,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.ActivityManager.RunningTaskInfo;
 import android.app.TaskInfo;
 import android.content.ComponentName;
 import android.content.Context;
@@ -60,7 +59,6 @@
 import com.android.wm.shell.sysui.KeyguardChangeListener;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.transition.FocusTransitionObserver;
 import com.android.wm.shell.transition.Transitions;
 
 import dagger.Lazy;
@@ -73,6 +71,7 @@
 import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Function;
+import java.util.function.IntPredicate;
 import java.util.function.Predicate;
 
 /**
@@ -198,9 +197,6 @@
     private final CompatUIStatusManager mCompatUIStatusManager;
 
     @NonNull
-    private final FocusTransitionObserver mFocusTransitionObserver;
-
-    @NonNull
     private final Optional<DesktopUserRepositories> mDesktopUserRepositories;
 
     public CompatUIController(@NonNull Context context,
@@ -217,8 +213,7 @@
             @NonNull CompatUIShellCommandHandler compatUIShellCommandHandler,
             @NonNull AccessibilityManager accessibilityManager,
             @NonNull CompatUIStatusManager compatUIStatusManager,
-            @NonNull Optional<DesktopUserRepositories> desktopUserRepositories,
-            @NonNull FocusTransitionObserver focusTransitionObserver) {
+            @NonNull Optional<DesktopUserRepositories> desktopUserRepositories) {
         mContext = context;
         mShellController = shellController;
         mDisplayController = displayController;
@@ -235,7 +230,6 @@
                 DISAPPEAR_DELAY_MS, flags);
         mCompatUIStatusManager = compatUIStatusManager;
         mDesktopUserRepositories = desktopUserRepositories;
-        mFocusTransitionObserver = focusTransitionObserver;
         shellInit.addInitCallback(this::onInit, this);
     }
 
@@ -412,8 +406,7 @@
         // start tracking the buttons visibility for this task.
         if (mTopActivityTaskId != taskInfo.taskId
                 && !taskInfo.isTopActivityTransparent
-                && taskInfo.isVisible
-                && mFocusTransitionObserver.hasGlobalFocus((RunningTaskInfo) taskInfo)) {
+                && taskInfo.isVisible && taskInfo.isFocused) {
             mTopActivityTaskId = taskInfo.taskId;
             setHasShownUserAspectRatioSettingsButton(false);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 2600bcc..23a0f4a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -272,8 +272,7 @@
             @NonNull CompatUIState compatUIState,
             @NonNull CompatUIComponentIdGenerator componentIdGenerator,
             @NonNull CompatUIComponentFactory compatUIComponentFactory,
-            CompatUIStatusManager compatUIStatusManager,
-            @NonNull FocusTransitionObserver focusTransitionObserver) {
+            CompatUIStatusManager compatUIStatusManager) {
         if (!context.getResources().getBoolean(R.bool.config_enableCompatUIController)) {
             return Optional.empty();
         }
@@ -298,8 +297,7 @@
                         compatUIShellCommandHandler.get(),
                         accessibilityManager.get(),
                         compatUIStatusManager,
-                        desktopUserRepositories,
-                        focusTransitionObserver));
+                        desktopUserRepositories));
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
index 636549f..a6f8150 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
@@ -176,7 +176,6 @@
      * transition
      */
     @Ignore("TODO(b/356277166): enable the tablet test")
-    @Postsubmit
     @Test
     open fun pipAppLayerPlusLetterboxCoversFullScreenOnStartTablet() {
         assumeTrue(tapl.isTablet)
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfEnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfEnterPipToOtherOrientation.kt
index 4987ab7..d65f158 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfEnterPipToOtherOrientation.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfEnterPipToOtherOrientation.kt
@@ -78,7 +78,6 @@
     }
 
     @Ignore("TODO(b/356277166): enable the tablet test")
-    @Presubmit
     @Test
     override fun pipAppLayerPlusLetterboxCoversFullScreenOnStartTablet() {
         // Test app and pip app should covers the entire screen on start.
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index 784e190..b5c9fa1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -62,11 +62,12 @@
 import com.android.wm.shell.desktopmode.DesktopUserRepositories;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.transition.FocusTransitionObserver;
 import com.android.wm.shell.transition.Transitions;
 
 import dagger.Lazy;
 
+import java.util.Optional;
+
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -76,8 +77,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.Optional;
-
 /**
  * Tests for {@link CompatUIController}.
  *
@@ -128,8 +127,6 @@
     private DesktopUserRepositories mDesktopUserRepositories;
     @Mock
     private DesktopRepository mDesktopRepository;
-    @Mock
-    private FocusTransitionObserver mFocusTransitionObserver;
 
     @Captor
     ArgumentCaptor<OnInsetsChangedListener> mOnInsetsChangedListenerCaptor;
@@ -165,8 +162,7 @@
                 mMockDisplayController, mMockDisplayInsetsController, mMockImeController,
                 mMockSyncQueue, mMockExecutor, mMockTransitionsLazy, mDockStateReader,
                 mCompatUIConfiguration, mCompatUIShellCommandHandler, mAccessibilityManager,
-                mCompatUIStatusManager, Optional.of(mDesktopUserRepositories),
-                mFocusTransitionObserver) {
+                mCompatUIStatusManager, Optional.of(mDesktopUserRepositories)) {
             @Override
             CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo,
                     ShellTaskOrganizer.TaskListener taskListener) {
@@ -284,7 +280,6 @@
         doReturn(false).when(mMockRestartDialogLayout).updateCompatInfo(any(), any(), anyBoolean());
 
         TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
-        when(mFocusTransitionObserver.hasGlobalFocus((RunningTaskInfo) taskInfo)).thenReturn(true);
         mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
 
         verify(mController).createCompatUiWindowManager(any(), eq(taskInfo), eq(mMockTaskListener));
@@ -416,7 +411,6 @@
         // Verify button remains hidden while IME is showing.
         TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
         mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
-        when(mFocusTransitionObserver.hasGlobalFocus((RunningTaskInfo) taskInfo)).thenReturn(false);
 
         verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener,
                 /* canShow= */ false);
@@ -449,7 +443,6 @@
         // Verify button remains hidden while keyguard is showing.
         TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
         mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
-        when(mFocusTransitionObserver.hasGlobalFocus((RunningTaskInfo) taskInfo)).thenReturn(false);
 
         verify(mMockCompatLayout).updateCompatInfo(taskInfo, mMockTaskListener,
                 /* canShow= */ false);
@@ -530,7 +523,6 @@
     @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
     public void testRestartLayoutRecreatedIfNeeded() {
         final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
-        when(mFocusTransitionObserver.hasGlobalFocus((RunningTaskInfo) taskInfo)).thenReturn(false);
         doReturn(true).when(mMockRestartDialogLayout)
                 .needsToBeRecreated(any(TaskInfo.class),
                         any(ShellTaskOrganizer.TaskListener.class));
@@ -546,7 +538,6 @@
     @RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
     public void testRestartLayoutNotRecreatedIfNotNeeded() {
         final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
-        when(mFocusTransitionObserver.hasGlobalFocus((RunningTaskInfo) taskInfo)).thenReturn(false);
         doReturn(false).when(mMockRestartDialogLayout)
                 .needsToBeRecreated(any(TaskInfo.class),
                         any(ShellTaskOrganizer.TaskListener.class));
@@ -567,8 +558,7 @@
 
         // Create new task
         final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
-                /* isVisible */ true);
-        when(mFocusTransitionObserver.hasGlobalFocus((RunningTaskInfo) taskInfo)).thenReturn(true);
+                /* isVisible */ true, /* isFocused */ true);
 
         // Simulate new task being shown
         mController.updateActiveTaskInfo(taskInfo);
@@ -584,8 +574,7 @@
     public void testUpdateActiveTaskInfo_newTask_notVisibleOrFocused_notUpdated() {
         // Create new task
         final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
-                /* isVisible */ true);
-        when(mFocusTransitionObserver.hasGlobalFocus((RunningTaskInfo) taskInfo)).thenReturn(true);
+                /* isVisible */ true, /* isFocused */ true);
 
         // Simulate task being shown
         mController.updateActiveTaskInfo(taskInfo);
@@ -603,8 +592,7 @@
 
         // Create visible but NOT focused task
         final TaskInfo taskInfo1 = createTaskInfo(DISPLAY_ID, newTaskId, /* hasSizeCompat= */ true,
-                /* isVisible */ true);
-        when(mFocusTransitionObserver.hasGlobalFocus((RunningTaskInfo) taskInfo)).thenReturn(false);
+                /* isVisible */ true, /* isFocused */ false);
 
         // Simulate new task being shown
         mController.updateActiveTaskInfo(taskInfo1);
@@ -616,8 +604,7 @@
 
         // Create focused but NOT visible task
         final TaskInfo taskInfo2 = createTaskInfo(DISPLAY_ID, newTaskId, /* hasSizeCompat= */ true,
-                /* isVisible */ false);
-        when(mFocusTransitionObserver.hasGlobalFocus((RunningTaskInfo) taskInfo)).thenReturn(true);
+                /* isVisible */ false, /* isFocused */ true);
 
         // Simulate new task being shown
         mController.updateActiveTaskInfo(taskInfo2);
@@ -629,8 +616,7 @@
 
         // Create NOT focused but NOT visible task
         final TaskInfo taskInfo3 = createTaskInfo(DISPLAY_ID, newTaskId, /* hasSizeCompat= */ true,
-                /* isVisible */ false);
-        when(mFocusTransitionObserver.hasGlobalFocus((RunningTaskInfo) taskInfo)).thenReturn(false);
+                /* isVisible */ false, /* isFocused */ false);
 
         // Simulate new task being shown
         mController.updateActiveTaskInfo(taskInfo3);
@@ -646,8 +632,7 @@
     public void testUpdateActiveTaskInfo_sameTask_notUpdated() {
         // Create new task
         final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
-                /* isVisible */ true);
-        when(mFocusTransitionObserver.hasGlobalFocus((RunningTaskInfo) taskInfo)).thenReturn(true);
+                /* isVisible */ true, /* isFocused */ true);
 
         // Simulate new task being shown
         mController.updateActiveTaskInfo(taskInfo);
@@ -675,8 +660,7 @@
     public void testUpdateActiveTaskInfo_transparentTask_notUpdated() {
         // Create new task
         final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
-                /* isVisible */ true);
-        when(mFocusTransitionObserver.hasGlobalFocus((RunningTaskInfo) taskInfo)).thenReturn(true);
+                /* isVisible */ true, /* isFocused */ true);
 
         // Simulate new task being shown
         mController.updateActiveTaskInfo(taskInfo);
@@ -694,8 +678,7 @@
 
         // Create transparent task
         final TaskInfo taskInfo1 = createTaskInfo(DISPLAY_ID, newTaskId, /* hasSizeCompat= */ true,
-                /* isVisible */ true, /* isTopActivityTransparent */ true);
-        when(mFocusTransitionObserver.hasGlobalFocus((RunningTaskInfo) taskInfo)).thenReturn(true);
+                /* isVisible */ true, /* isFocused */ true, /* isTopActivityTransparent */ true);
 
         // Simulate new task being shown
         mController.updateActiveTaskInfo(taskInfo1);
@@ -711,7 +694,6 @@
     public void testLetterboxEduLayout_notCreatedWhenLetterboxEducationIsDisabled() {
         TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
         taskInfo.appCompatTaskInfo.setLetterboxEducationEnabled(false);
-        when(mFocusTransitionObserver.hasGlobalFocus((RunningTaskInfo) taskInfo)).thenReturn(false);
 
         mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
 
@@ -725,7 +707,6 @@
     public void testUpdateActiveTaskInfo_removeAllComponentWhenInDesktopModeFlagEnabled() {
         TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
         when(mDesktopUserRepositories.getCurrent().getVisibleTaskCount(DISPLAY_ID)).thenReturn(0);
-        when(mFocusTransitionObserver.hasGlobalFocus((RunningTaskInfo) taskInfo)).thenReturn(false);
 
         mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
 
@@ -744,7 +725,6 @@
     public void testUpdateActiveTaskInfo_removeAllComponentWhenInDesktopModeFlagDisabled() {
         when(mDesktopUserRepositories.getCurrent().getVisibleTaskCount(DISPLAY_ID)).thenReturn(0);
         TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true);
-        when(mFocusTransitionObserver.hasGlobalFocus((RunningTaskInfo) taskInfo)).thenReturn(false);
 
         mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
 
@@ -759,22 +739,23 @@
 
     private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat) {
         return createTaskInfo(displayId, taskId, hasSizeCompat, /* isVisible */ false,
-                /* isTopActivityTransparent */ false);
+                /* isFocused */ false, /* isTopActivityTransparent */ false);
     }
 
     private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat,
-            boolean isVisible) {
+            boolean isVisible, boolean isFocused) {
         return createTaskInfo(displayId, taskId, hasSizeCompat,
-                isVisible, /* isTopActivityTransparent */ false);
+                isVisible, isFocused, /* isTopActivityTransparent */ false);
     }
 
     private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat,
-            boolean isVisible, boolean isTopActivityTransparent) {
+            boolean isVisible, boolean isFocused, boolean isTopActivityTransparent) {
         RunningTaskInfo taskInfo = new RunningTaskInfo();
         taskInfo.taskId = taskId;
         taskInfo.displayId = displayId;
         taskInfo.appCompatTaskInfo.setTopActivityInSizeCompat(hasSizeCompat);
         taskInfo.isVisible = isVisible;
+        taskInfo.isFocused = isFocused;
         taskInfo.isTopActivityTransparent = isTopActivityTransparent;
         taskInfo.appCompatTaskInfo.setLetterboxEducationEnabled(true);
         taskInfo.appCompatTaskInfo.setTopActivityLetterboxed(true);
diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt
index f8fb6b7..139ccfd 100644
--- a/libs/appfunctions/api/current.txt
+++ b/libs/appfunctions/api/current.txt
@@ -36,8 +36,7 @@
   public abstract class AppFunctionService extends android.app.Service {
     ctor public AppFunctionService();
     method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
-    method @MainThread public void onExecuteFunction(@NonNull com.android.extensions.appfunctions.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.content.pm.SigningInfo, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<com.android.extensions.appfunctions.ExecuteAppFunctionResponse,com.android.extensions.appfunctions.AppFunctionException>);
-    method @Deprecated @MainThread public abstract void onExecuteFunction(@NonNull com.android.extensions.appfunctions.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<com.android.extensions.appfunctions.ExecuteAppFunctionResponse,com.android.extensions.appfunctions.AppFunctionException>);
+    method @MainThread public abstract void onExecuteFunction(@NonNull com.android.extensions.appfunctions.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<com.android.extensions.appfunctions.ExecuteAppFunctionResponse,com.android.extensions.appfunctions.AppFunctionException>);
     field @NonNull public static final String BIND_APP_FUNCTION_SERVICE = "android.permission.BIND_APP_FUNCTION_SERVICE";
     field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService";
   }
diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java
index a09451e..81d9d81 100644
--- a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java
+++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java
@@ -25,7 +25,6 @@
 import android.annotation.SdkConstant;
 import android.app.Service;
 import android.content.Intent;
-import android.content.pm.SigningInfo;
 import android.os.Binder;
 import android.os.CancellationSignal;
 import android.os.IBinder;
@@ -82,7 +81,6 @@
                                 SidecarConverter.getSidecarExecuteAppFunctionRequest(
                                         platformRequest),
                                 callingPackage,
-                                callingPackageSigningInfo,
                                 cancellationSignal,
                                 new OutcomeReceiver<>() {
                                     @Override
@@ -129,52 +127,10 @@
      *
      * @param request The function execution request.
      * @param callingPackage The package name of the app that is requesting the execution.
-     * @param callingPackageSigningInfo The signing information of the app that is requesting the
-     *     execution.
      * @param cancellationSignal A signal to cancel the execution.
      * @param callback A callback to report back the result or error.
      */
     @MainThread
-    public void onExecuteFunction(
-            @NonNull ExecuteAppFunctionRequest request,
-            @NonNull String callingPackage,
-            @NonNull SigningInfo callingPackageSigningInfo,
-            @NonNull CancellationSignal cancellationSignal,
-            @NonNull OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException> callback) {
-        onExecuteFunction(request, callingPackage, cancellationSignal, callback);
-    }
-
-    /**
-     * Called by the system to execute a specific app function.
-     *
-     * <p>This method is the entry point for handling all app function requests in an app. When the
-     * system needs your AppFunctionService to perform a function, it will invoke this method.
-     *
-     * <p>Each function you've registered is identified by a unique identifier. This identifier
-     * doesn't need to be globally unique, but it must be unique within your app. For example, a
-     * function to order food could be identified as "orderFood". In most cases, this identifier is
-     * automatically generated by the AppFunctions SDK.
-     *
-     * <p>You can determine which function to execute by calling {@link
-     * ExecuteAppFunctionRequest#getFunctionIdentifier()}. This allows your service to route the
-     * incoming request to the appropriate logic for handling the specific function.
-     *
-     * <p>This method is always triggered in the main thread. You should run heavy tasks on a worker
-     * thread and dispatch the result with the given callback. You should always report back the
-     * result using the callback, no matter if the execution was successful or not.
-     *
-     * <p>This method also accepts a {@link CancellationSignal} that the app should listen to cancel
-     * the execution of function if requested by the system.
-     *
-     * @param request The function execution request.
-     * @param callingPackage The package name of the app that is requesting the execution.
-     * @param cancellationSignal A signal to cancel the execution.
-     * @param callback A callback to report back the result or error.
-     * @deprecated Use {@link #onExecuteFunction(ExecuteAppFunctionRequest, String, SigningInfo,
-     *     CancellationSignal, OutcomeReceiver)} instead.
-     */
-    @MainThread
-    @Deprecated
     public abstract void onExecuteFunction(
             @NonNull ExecuteAppFunctionRequest request,
             @NonNull String callingPackage,
diff --git a/libs/hwui/renderthread/HintSessionWrapper.h b/libs/hwui/renderthread/HintSessionWrapper.h
index 859cc57..4c96567 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.h
+++ b/libs/hwui/renderthread/HintSessionWrapper.h
@@ -20,6 +20,7 @@
 #include <private/performance_hint_private.h>
 
 #include <future>
+#include <memory>
 #include <optional>
 #include <vector>
 
diff --git a/media/TEST_MAPPING b/media/TEST_MAPPING
index e52e0b1..6a21496 100644
--- a/media/TEST_MAPPING
+++ b/media/TEST_MAPPING
@@ -1,7 +1,10 @@
 {
   "presubmit": [
     {
-      "name": "CtsMediaBetterTogetherTestCases"
+      "name": "CtsMediaRouterTestCases"
+    },
+    {
+      "name": "CtsMediaSessionTestCases"
     },
     {
       "name": "mediaroutertest"
diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java
index 7516d2e..e3d7902 100644
--- a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java
+++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminControllerFactory.java
@@ -22,6 +22,7 @@
 import static com.android.settingslib.enterprise.ManagedDeviceActionDisabledByAdminController.DEFAULT_FOREGROUND_USER_CHECKER;
 
 import android.app.admin.DevicePolicyManager;
+import android.app.supervision.SupervisionManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.hardware.biometrics.BiometricAuthenticator;
@@ -59,12 +60,18 @@
     }
 
     private static boolean isSupervisedDevice(Context context) {
-        DevicePolicyManager devicePolicyManager =
-                context.getSystemService(DevicePolicyManager.class);
-        ComponentName supervisionComponent =
-                devicePolicyManager.getProfileOwnerOrDeviceOwnerSupervisionComponent(
-                        new UserHandle(UserHandle.myUserId()));
-        return supervisionComponent != null;
+        if (android.app.supervision.flags.Flags.deprecateDpmSupervisionApis()) {
+            SupervisionManager supervisionManager =
+                    context.getSystemService(SupervisionManager.class);
+            return supervisionManager.isSupervisionEnabledForUser(UserHandle.myUserId());
+        } else {
+            DevicePolicyManager devicePolicyManager =
+                    context.getSystemService(DevicePolicyManager.class);
+            ComponentName supervisionComponent =
+                    devicePolicyManager.getProfileOwnerOrDeviceOwnerSupervisionComponent(
+                            new UserHandle(UserHandle.myUserId()));
+            return supervisionComponent != null;
+        }
     }
 
     /**
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index 4125a81f..fc61b1e 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -46,6 +46,7 @@
         Settings.Global.APP_AUTO_RESTRICTION_ENABLED,
         Settings.Global.AUTO_TIME,
         Settings.Global.AUTO_TIME_ZONE,
+        Settings.Global.TIME_ZONE_NOTIFICATIONS,
         Settings.Global.POWER_SOUNDS_ENABLED,
         Settings.Global.DOCK_SOUNDS_ENABLED,
         Settings.Global.CHARGING_SOUNDS_ENABLED,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 32d4580..c0e266f 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -102,6 +102,7 @@
                 });
         VALIDATORS.put(Global.AUTO_TIME, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.AUTO_TIME_ZONE, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Global.TIME_ZONE_NOTIFICATIONS, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.POWER_SOUNDS_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.DOCK_SOUNDS_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.CHARGING_SOUNDS_ENABLED, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index ef0bc3b..c1c3e04 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -212,10 +212,8 @@
     private static final String ERROR_IO_EXCEPTION = "io_exception";
     private static final String ERROR_FAILED_TO_RESTORE_SOFTAP_CONFIG =
         "failed_to_restore_softap_config";
-    private static final String ERROR_FAILED_TO_CONVERT_NETWORK_POLICIES =
-        "failed_to_convert_network_policies";
-    private static final String ERROR_UNKNOWN_BACKUP_SERIALIZATION_VERSION =
-        "unknown_backup_serialization_version";
+    private static final String ERROR_FAILED_TO_RESTORE_WIFI_CONFIG =
+        "failed_to_restore_wifi_config";
 
 
     // Name of the temporary file we use during full backup/restore.  This is
@@ -1438,7 +1436,6 @@
             try {
                 out.writeInt(NETWORK_POLICIES_BACKUP_VERSION);
                 out.writeInt(policies.length);
-                int numberOfPoliciesBackedUp = 0;
                 for (NetworkPolicy policy : policies) {
                     // We purposefully only backup policies that the user has
                     // defined; any inferred policies might include
@@ -1448,30 +1445,26 @@
                         out.writeByte(BackupUtils.NOT_NULL);
                         out.writeInt(marshaledPolicy.length);
                         out.write(marshaledPolicy);
-                        if (areAgentMetricsEnabled) {
-                            numberOfPoliciesBackedUp++;
-                        }
                     } else {
                         out.writeByte(BackupUtils.NULL);
                     }
                 }
-                if (areAgentMetricsEnabled) {
-                    numberOfSettingsPerKey.put(KEY_NETWORK_POLICIES, numberOfPoliciesBackedUp);
-                }
             } catch (IOException ioe) {
                 Log.e(TAG, "Failed to convert NetworkPolicies to byte array " + ioe.getMessage());
                 baos.reset();
-                mBackupRestoreEventLogger.logItemsBackupFailed(
-                    KEY_NETWORK_POLICIES,
-                    policies.length,
-                    ERROR_FAILED_TO_CONVERT_NETWORK_POLICIES);
             }
         }
         return baos.toByteArray();
     }
 
-    private byte[] getNewWifiConfigData() {
-        return mWifiManager.retrieveBackupData();
+    @VisibleForTesting
+    byte[] getNewWifiConfigData() {
+        byte[] data = mWifiManager.retrieveBackupData();
+        if (areAgentMetricsEnabled) {
+            // We're unable to determine how many settings this includes, so we'll just log 1.
+            numberOfSettingsPerKey.put(KEY_WIFI_NEW_CONFIG, 1);
+        }
+        return data;
     }
 
     private byte[] getLocaleSettings() {
@@ -1483,11 +1476,22 @@
         return localeList.toLanguageTags().getBytes();
     }
 
-    private void restoreNewWifiConfigData(byte[] bytes) {
+    @VisibleForTesting
+    void restoreNewWifiConfigData(byte[] bytes) {
         if (DEBUG_BACKUP) {
             Log.v(TAG, "Applying restored wifi data");
         }
-        mWifiManager.restoreBackupData(bytes);
+        if (areAgentMetricsEnabled) {
+            try {
+                mWifiManager.restoreBackupData(bytes);
+                mBackupRestoreEventLogger.logItemsRestored(KEY_WIFI_NEW_CONFIG, /* count= */ 1);
+            } catch (Exception e) {
+                mBackupRestoreEventLogger.logItemsRestoreFailed(
+                    KEY_WIFI_NEW_CONFIG, /* count= */ 1, ERROR_FAILED_TO_RESTORE_WIFI_CONFIG);
+            }
+        } else {
+            mWifiManager.restoreBackupData(bytes);
+        }
     }
 
     private void restoreNetworkPolicies(byte[] data) {
@@ -1498,10 +1502,6 @@
             try {
                 int version = in.readInt();
                 if (version < 1 || version > NETWORK_POLICIES_BACKUP_VERSION) {
-                    mBackupRestoreEventLogger.logItemsRestoreFailed(
-                            KEY_NETWORK_POLICIES,
-                            /* count= */ 1,
-                            ERROR_UNKNOWN_BACKUP_SERIALIZATION_VERSION);
                     throw new BackupUtils.BadVersionException(
                             "Unknown Backup Serialization Version");
                 }
@@ -1518,15 +1518,10 @@
                 }
                 // Only set the policies if there was no error in the restore operation
                 networkPolicyManager.setNetworkPolicies(policies);
-                mBackupRestoreEventLogger.logItemsRestored(KEY_NETWORK_POLICIES, policies.length);
             } catch (NullPointerException | IOException | BackupUtils.BadVersionException
                     | DateTimeException e) {
                 // NPE can be thrown when trying to instantiate a NetworkPolicy
                 Log.e(TAG, "Failed to convert byte array to NetworkPolicies " + e.getMessage());
-                mBackupRestoreEventLogger.logItemsRestoreFailed(
-                        KEY_NETWORK_POLICIES,
-                        /* count= */ 1,
-                        ERROR_FAILED_TO_CONVERT_NETWORK_POLICIES);
             }
         }
     }
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java
index 95dd0db..6e5b602c 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java
@@ -16,6 +16,7 @@
 
 package com.android.providers.settings;
 
+import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_WIFI_NEW_CONFIG;
 import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_SOFTAP_CONFIG;
 
 import static junit.framework.Assert.assertEquals;
@@ -28,6 +29,8 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.when;
 
 import android.annotation.Nullable;
@@ -69,6 +72,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
@@ -834,6 +838,74 @@
         assertNull(getLoggingResultForDatatype(KEY_SOFTAP_CONFIG, mAgentUnderTest));
     }
 
+    @Test
+    @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+    public void getNewWifiConfigData_flagIsEnabled_numberOfSettingsInKeyAreRecorded() {
+        mAgentUnderTest.onCreate(
+            UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.BACKUP);
+        when(mWifiManager.retrieveBackupData()).thenReturn(null);
+
+        mAgentUnderTest.getNewWifiConfigData();
+
+        assertEquals(mAgentUnderTest.getNumberOfSettingsPerKey(KEY_WIFI_NEW_CONFIG), 1);
+    }
+
+    @Test
+    @DisableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+    public void getNewWifiConfigData_flagIsNotEnabled_numberOfSettingsInKeyAreNotRecorded() {
+        mAgentUnderTest.onCreate(
+            UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.BACKUP);
+        when(mWifiManager.retrieveBackupData()).thenReturn(null);
+
+        mAgentUnderTest.getNewWifiConfigData();
+
+        assertEquals(mAgentUnderTest.getNumberOfSettingsPerKey(KEY_WIFI_NEW_CONFIG), 0);
+    }
+
+    @Test
+    @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+    public void
+        restoreNewWifiConfigData_flagIsEnabled_restoreIsSuccessful_successMetricsAreLogged() {
+        mAgentUnderTest.onCreate(
+            UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE);
+        doNothing().when(mWifiManager).restoreBackupData(any());
+
+        mAgentUnderTest.restoreNewWifiConfigData(new byte[] {});
+
+        DataTypeResult loggingResult =
+            getLoggingResultForDatatype(KEY_WIFI_NEW_CONFIG, mAgentUnderTest);
+        assertNotNull(loggingResult);
+        assertEquals(loggingResult.getSuccessCount(), 1);
+    }
+
+    @Test
+    @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+    public void
+        restoreNewWifiConfigData_flagIsEnabled_restoreIsNotSuccessful_failureMetricsAreLogged() {
+        mAgentUnderTest.onCreate(
+            UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE);
+        doThrow(new RuntimeException()).when(mWifiManager).restoreBackupData(any());
+
+        mAgentUnderTest.restoreNewWifiConfigData(new byte[] {});
+
+        DataTypeResult loggingResult =
+            getLoggingResultForDatatype(KEY_WIFI_NEW_CONFIG, mAgentUnderTest);
+        assertNotNull(loggingResult);
+        assertEquals(loggingResult.getFailCount(), 1);
+    }
+
+    @Test
+    @DisableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+    public void restoreNewWifiConfigData_flagIsNotEnabled_metricsAreNotLogged() {
+        mAgentUnderTest.onCreate(
+            UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE);
+        doNothing().when(mWifiManager).restoreBackupData(any());
+
+        mAgentUnderTest.restoreNewWifiConfigData(new byte[] {});
+
+        assertNull(getLoggingResultForDatatype(KEY_WIFI_NEW_CONFIG, mAgentUnderTest));
+    }
+
     private byte[] generateBackupData(Map<String, String> keyValueData) {
         int totalBytes = 0;
         for (String key : keyValueData.keySet()) {
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt
index 5f1f588..c7d6e8a 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt
@@ -34,6 +34,7 @@
 import androidx.compose.ui.input.pointer.AwaitPointerEventScope
 import androidx.compose.ui.input.pointer.PointerEvent
 import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.PointerEventType
 import androidx.compose.ui.input.pointer.PointerId
 import androidx.compose.ui.input.pointer.PointerInputChange
 import androidx.compose.ui.input.pointer.PointerInputScope
@@ -168,6 +169,12 @@
     CompositionLocalConsumerModifierNode,
     OrientationAware {
     private val nestedScrollDispatcher = NestedScrollDispatcher()
+    private var trackWheelScroll: SuspendingPointerInputModifierNode? = null
+        set(value) {
+            field?.let { undelegate(it) }
+            field = value?.also { delegate(it) }
+        }
+
     private var trackDownPositionDelegate: SuspendingPointerInputModifierNode? = null
         set(value) {
             field?.let { undelegate(it) }
@@ -189,6 +196,7 @@
      * This is use to track the started position of a drag started on a nested scrollable.
      */
     private var lastFirstDown: Offset? = null
+    private var lastEventWasScrollWheel: Boolean = false
 
     /** The pointers currently down, in order of which they were done and mapping to their type. */
     private val pointersDown = linkedMapOf<PointerId, PointerType>()
@@ -218,8 +226,11 @@
         nestedScrollController?.ensureOnDragStoppedIsCalled()
         nestedScrollController = null
 
-        if (!enabled && trackDownPositionDelegate != null) {
+        if (!enabled && trackWheelScroll != null) {
+            check(trackDownPositionDelegate != null)
             check(detectDragsDelegate != null)
+
+            trackWheelScroll = null
             trackDownPositionDelegate = null
             detectDragsDelegate = null
         }
@@ -232,17 +243,22 @@
     ) {
         if (!enabled) return
 
-        if (trackDownPositionDelegate == null) {
+        if (trackWheelScroll == null) {
+            check(trackDownPositionDelegate == null)
             check(detectDragsDelegate == null)
+
+            trackWheelScroll = SuspendingPointerInputModifierNode { trackWheelScroll() }
             trackDownPositionDelegate = SuspendingPointerInputModifierNode { trackDownPosition() }
             detectDragsDelegate = SuspendingPointerInputModifierNode { detectDrags() }
         }
 
+        checkNotNull(trackWheelScroll).onPointerEvent(pointerEvent, pass, bounds)
         checkNotNull(trackDownPositionDelegate).onPointerEvent(pointerEvent, pass, bounds)
         checkNotNull(detectDragsDelegate).onPointerEvent(pointerEvent, pass, bounds)
     }
 
     override fun onCancelPointerInput() {
+        trackWheelScroll?.onCancelPointerInput()
         trackDownPositionDelegate?.onCancelPointerInput()
         detectDragsDelegate?.onCancelPointerInput()
     }
@@ -457,6 +473,13 @@
      * ===============================
      */
 
+    private suspend fun PointerInputScope.trackWheelScroll() {
+        awaitEachGesture {
+            val event = awaitPointerEvent(pass = PointerEventPass.Initial)
+            lastEventWasScrollWheel = event.type == PointerEventType.Scroll
+        }
+    }
+
     private suspend fun PointerInputScope.trackDownPosition() {
         awaitEachGesture {
             try {
@@ -501,7 +524,12 @@
         }
 
         val sign = offset.sign
-        if (nestedScrollController == null && draggable.shouldConsumeNestedScroll(sign)) {
+        if (
+            nestedScrollController == null &&
+                // TODO(b/388231324): Remove this.
+                !lastEventWasScrollWheel &&
+                draggable.shouldConsumeNestedScroll(sign)
+        ) {
             val startedPosition = checkNotNull(lastFirstDown) { "lastFirstDown is not set" }
 
             // TODO(b/382665591): Ensure that there is at least one pointer down.
diff --git a/packages/SystemUI/compose/core/tests/AndroidManifest.xml b/packages/SystemUI/compose/core/tests/AndroidManifest.xml
index 28f80d4..7c721b9 100644
--- a/packages/SystemUI/compose/core/tests/AndroidManifest.xml
+++ b/packages/SystemUI/compose/core/tests/AndroidManifest.xml
@@ -15,6 +15,7 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     package="com.android.compose.core.tests" >
 
     <application>
@@ -23,7 +24,8 @@
         <activity
             android:name="androidx.activity.ComponentActivity"
             android:theme="@android:style/Theme.DeviceDefault.DayNight"
-            android:exported="true" />
+            android:exported="true"
+            tools:replace="android:theme" />
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt
index 7f70e97..f9cf495 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt
@@ -18,6 +18,8 @@
 
 import androidx.compose.foundation.ScrollState
 import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.rememberScrollableState
+import androidx.compose.foundation.gestures.scrollable
 import androidx.compose.foundation.horizontalScroll
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
@@ -35,6 +37,7 @@
 import androidx.compose.ui.input.pointer.PointerInputChange
 import androidx.compose.ui.input.pointer.PointerType
 import androidx.compose.ui.platform.LocalViewConfiguration
+import androidx.compose.ui.test.ScrollWheel
 import androidx.compose.ui.test.junit4.ComposeContentTestRule
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onRoot
@@ -710,6 +713,33 @@
         rule.onRoot().performTouchInput { down(center) }
     }
 
+    @Test
+    // TODO(b/388231324): Remove this.
+    fun nestedScrollWithMouseWheelIsIgnored() {
+        val draggable = TestDraggable()
+        val touchSlop =
+            rule.setContentWithTouchSlop {
+                Box(
+                    Modifier.fillMaxSize()
+                        .nestedDraggable(draggable, orientation)
+                        .scrollable(rememberScrollableState { 0f }, orientation)
+                )
+            }
+
+        rule.onRoot().performMouseInput {
+            enter(center)
+            scroll(
+                touchSlop + 1f,
+                when (orientation) {
+                    Orientation.Horizontal -> ScrollWheel.Horizontal
+                    Orientation.Vertical -> ScrollWheel.Vertical
+                },
+            )
+        }
+
+        assertThat(draggable.onDragStartedCalled).isFalse()
+    }
+
     private fun ComposeContentTestRule.setContentWithTouchSlop(
         content: @Composable () -> Unit
     ): Float {
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 6bb579d..b41c558 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
@@ -60,17 +60,12 @@
      * Stop the current drag with the given [velocity].
      *
      * @param velocity The velocity of the drag when it stopped.
-     * @param canChangeContent Whether the content can be changed as a result of this drag.
      * @return the consumed [velocity] when the animation complete
      */
-    suspend fun onStop(velocity: Float, canChangeContent: Boolean): Float
+    suspend fun onStop(velocity: Float): Float
 
-    /**
-     * Cancels the current drag.
-     *
-     * @param canChangeContent Whether the content can be changed as a result of this drag.
-     */
-    fun onCancel(canChangeContent: Boolean)
+    /** Cancels the current drag. */
+    fun onCancel()
 }
 
 internal class DraggableHandlerImpl(
@@ -295,17 +290,16 @@
         return newOffset - previousOffset
     }
 
-    override suspend fun onStop(velocity: Float, canChangeContent: Boolean): Float {
+    override suspend fun onStop(velocity: Float): Float {
         // To ensure that any ongoing animation completes gracefully and avoids an undefined state,
         // we execute the actual `onStop` logic in a non-cancellable context. This prevents the
         // coroutine from being cancelled prematurely, which could interrupt the animation.
         // TODO(b/378470603) Remove this check once NestedDraggable is used to handle drags.
-        return withContext(NonCancellable) { onStop(velocity, canChangeContent, swipeAnimation) }
+        return withContext(NonCancellable) { onStop(velocity, swipeAnimation) }
     }
 
     private suspend fun <T : ContentKey> onStop(
         velocity: Float,
-        canChangeContent: Boolean,
 
         // Important: Make sure that this has the same name as [this.swipeAnimation] so that all the
         // code here references the current animation when [onDragStopped] is called, otherwise the
@@ -319,35 +313,27 @@
         }
 
         val fromContent = swipeAnimation.fromContent
+        // If we are halfway between two contents, we check what the target will be based on
+        // the velocity and offset of the transition, then we launch the animation.
+
+        val toContent = swipeAnimation.toContent
+
+        // Compute the destination content (and therefore offset) to settle in.
+        val offset = swipeAnimation.dragOffset
+        val distance = swipeAnimation.distance()
         val targetContent =
-            if (canChangeContent) {
-                // If we are halfway between two contents, we check what the target will be based on
-                // the velocity and offset of the transition, then we launch the animation.
-
-                val toContent = swipeAnimation.toContent
-
-                // Compute the destination content (and therefore offset) to settle in.
-                val offset = swipeAnimation.dragOffset
-                val distance = swipeAnimation.distance()
-                if (
-                    distance != DistanceUnspecified &&
-                        shouldCommitSwipe(
-                            offset = offset,
-                            distance = distance,
-                            velocity = velocity,
-                            wasCommitted = swipeAnimation.currentContent == toContent,
-                            requiresFullDistanceSwipe = swipeAnimation.requiresFullDistanceSwipe,
-                        )
-                ) {
-                    toContent
-                } else {
-                    fromContent
-                }
+            if (
+                distance != DistanceUnspecified &&
+                    shouldCommitSwipe(
+                        offset = offset,
+                        distance = distance,
+                        velocity = velocity,
+                        wasCommitted = swipeAnimation.currentContent == toContent,
+                        requiresFullDistanceSwipe = swipeAnimation.requiresFullDistanceSwipe,
+                    )
+            ) {
+                toContent
             } else {
-                // We are doing an overscroll preview animation between scenes.
-                check(fromContent == swipeAnimation.currentContent) {
-                    "canChangeContent is false but currentContent != fromContent"
-                }
                 fromContent
             }
 
@@ -423,10 +409,8 @@
         }
     }
 
-    override fun onCancel(canChangeContent: Boolean) {
-        swipeAnimation.contentTransition.coroutineScope.launch {
-            onStop(velocity = 0f, canChangeContent = canChangeContent)
-        }
+    override fun onCancel() {
+        swipeAnimation.contentTransition.coroutineScope.launch { onStop(velocity = 0f) }
     }
 }
 
@@ -519,11 +503,11 @@
         }
 
         override suspend fun OnStopScope.onStop(initialVelocity: Float): Float {
-            return dragController.onStop(velocity = initialVelocity, canChangeContent = true)
+            return dragController.onStop(velocity = initialVelocity)
         }
 
         override fun onCancel() {
-            dragController.onCancel(canChangeContent = true)
+            dragController.onCancel()
         }
 
         /**
@@ -547,9 +531,9 @@
 private object NoOpDragController : DragController {
     override fun onDrag(delta: Float) = 0f
 
-    override suspend fun onStop(velocity: Float, canChangeContent: Boolean) = 0f
+    override suspend fun onStop(velocity: Float) = 0f
 
-    override fun onCancel(canChangeContent: Boolean) {
+    override fun onCancel() {
         /* do nothing */
     }
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index f5f01d4..89320f13 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -307,13 +307,13 @@
                                             velocityTracker.calculateVelocity(maxVelocity)
                                         }
                                         .toFloat(),
-                                onFling = { controller.onStop(it, canChangeContent = true) },
+                                onFling = { controller.onStop(it) },
                             )
                         },
                         onDragCancel = { controller ->
                             startFlingGesture(
                                 initialVelocity = 0f,
-                                onFling = { controller.onStop(it, canChangeContent = true) },
+                                onFling = { controller.onStop(it) },
                             )
                         },
                         swipeDetector = swipeDetector,
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 5a35d11..dbac62f 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
@@ -247,32 +247,26 @@
 
         suspend fun DragController.onDragStoppedAnimateNow(
             velocity: Float,
-            canChangeScene: Boolean = true,
             onAnimationStart: () -> Unit,
             onAnimationEnd: (Float) -> Unit,
         ) {
-            val velocityConsumed = onDragStoppedAnimateLater(velocity, canChangeScene)
+            val velocityConsumed = onDragStoppedAnimateLater(velocity)
             onAnimationStart()
             onAnimationEnd(velocityConsumed.await())
         }
 
         suspend fun DragController.onDragStoppedAnimateNow(
             velocity: Float,
-            canChangeScene: Boolean = true,
             onAnimationStart: () -> Unit,
         ) =
             onDragStoppedAnimateNow(
                 velocity = velocity,
-                canChangeScene = canChangeScene,
                 onAnimationStart = onAnimationStart,
                 onAnimationEnd = {},
             )
 
-        fun DragController.onDragStoppedAnimateLater(
-            velocity: Float,
-            canChangeScene: Boolean = true,
-        ): Deferred<Float> {
-            val velocityConsumed = testScope.async { onStop(velocity, canChangeScene) }
+        fun DragController.onDragStoppedAnimateLater(velocity: Float): Deferred<Float> {
+            val velocityConsumed = testScope.async { onStop(velocity) }
             testScope.testScheduler.runCurrent()
             return velocityConsumed
         }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
index 4153350..5c6f91b 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
@@ -72,12 +72,12 @@
             return delta
         }
 
-        override suspend fun onStop(velocity: Float, canChangeContent: Boolean): Float {
+        override suspend fun onStop(velocity: Float): Float {
             onStop.invoke(velocity)
             return velocity
         }
 
-        override fun onCancel(canChangeContent: Boolean) {
+        override fun onCancel() {
             error("MultiPointerDraggable never calls onCancel()")
         }
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
index 7c88d76..183e4d6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
@@ -24,6 +24,7 @@
 import android.os.SystemClock
 import android.view.KeyEvent
 import android.view.KeyEvent.ACTION_DOWN
+import android.view.KeyEvent.ACTION_UP
 import android.view.KeyEvent.KEYCODE_A
 import android.view.KeyEvent.META_ALT_ON
 import android.view.KeyEvent.META_CTRL_ON
@@ -540,11 +541,7 @@
             simpleShortcutCategory(System, "System apps", "Take a note"),
             simpleShortcutCategory(System, "System controls", "Take screenshot"),
             simpleShortcutCategory(System, "System controls", "Go back"),
-            simpleShortcutCategory(
-                MultiTasking,
-                "Split screen",
-                "Switch to full screen",
-            ),
+            simpleShortcutCategory(MultiTasking, "Split screen", "Switch to full screen"),
             simpleShortcutCategory(
                 MultiTasking,
                 "Split screen",
@@ -704,7 +701,7 @@
             android.view.KeyEvent(
                 /* downTime = */ SystemClock.uptimeMillis(),
                 /* eventTime = */ SystemClock.uptimeMillis(),
-                /* action = */ ACTION_DOWN,
+                /* action = */ ACTION_UP,
                 /* code = */ KEYCODE_A,
                 /* repeat = */ 0,
                 /* metaState = */ 0,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt
index 755c218..d9d34f5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt
@@ -92,13 +92,14 @@
             viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
             val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
 
-            assertThat(uiState).isEqualTo(
-                AddShortcutDialog(
-                    shortcutLabel = "Standard shortcut",
-                    defaultCustomShortcutModifierKey =
-                    ShortcutKey.Icon.ResIdIcon(R.drawable.ic_ksh_key_meta),
+            assertThat(uiState)
+                .isEqualTo(
+                    AddShortcutDialog(
+                        shortcutLabel = "Standard shortcut",
+                        defaultCustomShortcutModifierKey =
+                            ShortcutKey.Icon.ResIdIcon(R.drawable.ic_ksh_key_meta),
+                    )
                 )
-            )
         }
     }
 
@@ -137,8 +138,7 @@
         testScope.runTest {
             val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
             viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
-            assertThat((uiState as AddShortcutDialog).pressedKeys)
-                .isEmpty()
+            assertThat((uiState as AddShortcutDialog).pressedKeys).isEmpty()
         }
     }
 
@@ -161,8 +161,7 @@
             val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
             viewModel.onShortcutCustomizationRequested(allAppsShortcutAddRequest)
 
-            assertThat((uiState as AddShortcutDialog).errorMessage)
-                .isEmpty()
+            assertThat((uiState as AddShortcutDialog).errorMessage).isEmpty()
         }
     }
 
@@ -244,32 +243,34 @@
     }
 
     @Test
-    fun onKeyPressed_handlesKeyEvents_whereActionKeyIsAlsoPressed() {
+    fun onShortcutKeyCombinationSelected_handlesKeyEvents_whereActionKeyIsAlsoPressed() {
         testScope.runTest {
             viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
-            val isHandled = viewModel.onKeyPressed(keyDownEventWithActionKeyPressed)
+            val isHandled =
+                viewModel.onShortcutKeyCombinationSelected(keyDownEventWithActionKeyPressed)
 
             assertThat(isHandled).isTrue()
         }
     }
 
     @Test
-    fun onKeyPressed_doesNotHandleKeyEvents_whenActionKeyIsNotAlsoPressed() {
+    fun onShortcutKeyCombinationSelected_doesNotHandleKeyEvents_whenActionKeyIsNotAlsoPressed() {
         testScope.runTest {
             viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
-            val isHandled = viewModel.onKeyPressed(keyDownEventWithoutActionKeyPressed)
+            val isHandled =
+                viewModel.onShortcutKeyCombinationSelected(keyDownEventWithoutActionKeyPressed)
 
             assertThat(isHandled).isFalse()
         }
     }
 
     @Test
-    fun onKeyPressed_convertsKeyEventsAndUpdatesUiStatesPressedKey() {
+    fun onShortcutKeyCombinationSelected_convertsKeyEventsAndUpdatesUiStatesPressedKey() {
         testScope.runTest {
             val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
             viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
-            viewModel.onKeyPressed(keyDownEventWithActionKeyPressed)
-            viewModel.onKeyPressed(keyUpEventWithActionKeyPressed)
+            viewModel.onShortcutKeyCombinationSelected(keyDownEventWithActionKeyPressed)
+            viewModel.onShortcutKeyCombinationSelected(keyUpEventWithActionKeyPressed)
 
             // Note that Action Key is excluded as it's already displayed on the UI
             assertThat((uiState as AddShortcutDialog).pressedKeys)
@@ -282,8 +283,8 @@
         testScope.runTest {
             val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
             viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
-            viewModel.onKeyPressed(keyDownEventWithActionKeyPressed)
-            viewModel.onKeyPressed(keyUpEventWithActionKeyPressed)
+            viewModel.onShortcutKeyCombinationSelected(keyDownEventWithActionKeyPressed)
+            viewModel.onShortcutKeyCombinationSelected(keyUpEventWithActionKeyPressed)
 
             // Note that Action Key is excluded as it's already displayed on the UI
             assertThat((uiState as AddShortcutDialog).pressedKeys)
@@ -292,16 +293,15 @@
             // Close the dialog and show it again
             viewModel.onDialogDismissed()
             viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
-            assertThat((uiState as AddShortcutDialog).pressedKeys)
-                .isEmpty()
+            assertThat((uiState as AddShortcutDialog).pressedKeys).isEmpty()
         }
     }
 
     private suspend fun openAddShortcutDialogAndSetShortcut() {
         viewModel.onShortcutCustomizationRequested(allAppsShortcutAddRequest)
 
-        viewModel.onKeyPressed(keyDownEventWithActionKeyPressed)
-        viewModel.onKeyPressed(keyUpEventWithActionKeyPressed)
+        viewModel.onShortcutKeyCombinationSelected(keyDownEventWithActionKeyPressed)
+        viewModel.onShortcutKeyCombinationSelected(keyUpEventWithActionKeyPressed)
 
         viewModel.onSetShortcut()
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index b3417b9..c44f27e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -46,8 +46,6 @@
 import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
-import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
@@ -76,7 +74,6 @@
     private val configRepository by lazy { kosmos.fakeConfigurationRepository }
     private val bouncerRepository by lazy { kosmos.keyguardBouncerRepository }
     private val shadeRepository by lazy { kosmos.shadeRepository }
-    private val powerInteractor by lazy { kosmos.powerInteractor }
     private val keyguardRepository by lazy { kosmos.keyguardRepository }
     private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
 
@@ -444,7 +441,6 @@
             repository.setDozeTransitionModel(
                 DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
             )
-            powerInteractor.setAwakeForTest()
             advanceTimeBy(1000L)
 
             assertThat(isAbleToDream).isEqualTo(false)
@@ -460,9 +456,6 @@
             repository.setDozeTransitionModel(
                 DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
             )
-            powerInteractor.setAwakeForTest()
-            runCurrent()
-
             // After some delay, still false
             advanceTimeBy(300L)
             assertThat(isAbleToDream).isEqualTo(false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt
index b069855..98e3c68 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt
@@ -36,6 +36,7 @@
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
@@ -406,4 +407,48 @@
             // It should not have any effect.
             assertEquals(listOf(false, true, false, true), canWake)
         }
+
+    @Test
+    @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    fun testCanWakeDirectlyToGone_falseAsSoonAsTransitionsAwayFromGone() =
+        testScope.runTest {
+            val canWake by collectValues(underTest.canWakeDirectlyToGone)
+
+            assertEquals(
+                listOf(
+                    false // Defaults to false.
+                ),
+                canWake,
+            )
+
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.GONE,
+                testScope,
+            )
+
+            assertEquals(
+                listOf(
+                    false,
+                    true, // Because we're GONE.
+                ),
+                canWake,
+            )
+
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.GONE,
+                testScope = testScope,
+                throughTransitionState = TransitionState.RUNNING,
+            )
+
+            assertEquals(
+                listOf(
+                    false,
+                    true,
+                    false, // False as soon as we start a transition away from GONE.
+                ),
+                canWake,
+            )
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt
index feaf06a..ade7614 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModelTest.kt
@@ -16,10 +16,13 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_BOUNCER_UI_REVAMP
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.BrokenWithSceneContainer
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -72,6 +75,28 @@
         }
 
     @Test
+    @EnableFlags(FLAG_BOUNCER_UI_REVAMP)
+    @BrokenWithSceneContainer(388068805)
+    fun notifications_areFullyVisible_whenShadeIsOpen() =
+        testScope.runTest {
+            val values by collectValues(underTest.notificationAlpha)
+            kosmos.bouncerWindowBlurTestUtil.shadeExpanded(true)
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                listOf(
+                    step(0f, TransitionState.STARTED),
+                    step(0.1f),
+                    step(0.2f),
+                    step(0.3f),
+                    step(1f),
+                ),
+                testScope,
+            )
+
+            values.forEach { assertThat(it).isEqualTo(1f) }
+        }
+
+    @Test
     fun blurRadiusGoesToMaximumWhenShadeIsExpanded() =
         testScope.runTest {
             val values by collectValues(underTest.windowBlurRadius)
@@ -88,6 +113,25 @@
         }
 
     @Test
+    @EnableFlags(FLAG_BOUNCER_UI_REVAMP)
+    @BrokenWithSceneContainer(388068805)
+    fun notificationBlur_isNonZero_whenShadeIsExpanded() =
+        testScope.runTest {
+            val values by collectValues(underTest.notificationBlurRadius)
+
+            kosmos.bouncerWindowBlurTestUtil.shadeExpanded(true)
+
+            kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius(
+                transitionProgress = listOf(0f, 0f, 0.1f, 0.2f, 0.3f, 1f),
+                startValue = kosmos.blurConfig.maxBlurRadiusPx / 3.0f,
+                endValue = kosmos.blurConfig.maxBlurRadiusPx / 3.0f,
+                transitionFactory = ::step,
+                actualValuesProvider = { values },
+                checkInterpolatedValues = false,
+            )
+        }
+
+    @Test
     fun blurRadiusGoesFromMinToMaxWhenShadeIsNotExpanded() =
         testScope.runTest {
             val values by collectValues(underTest.windowBlurRadius)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
index d909c5a..914094f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
@@ -16,9 +16,11 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.FlagsParameterization
 import androidx.test.filters.SmallTest
 import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.systemui.Flags.FLAG_BOUNCER_UI_REVAMP
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
@@ -153,7 +155,7 @@
         }
 
     @Test
-    @BrokenWithSceneContainer(330311871)
+    @BrokenWithSceneContainer(388068805)
     fun blurRadiusIsMaxWhenShadeIsExpanded() =
         testScope.runTest {
             val values by collectValues(underTest.windowBlurRadius)
@@ -170,7 +172,7 @@
         }
 
     @Test
-    @BrokenWithSceneContainer(330311871)
+    @BrokenWithSceneContainer(388068805)
     fun blurRadiusGoesFromMinToMaxWhenShadeIsNotExpanded() =
         testScope.runTest {
             val values by collectValues(underTest.windowBlurRadius)
@@ -185,6 +187,44 @@
             )
         }
 
+    @Test
+    @EnableFlags(FLAG_BOUNCER_UI_REVAMP)
+    @BrokenWithSceneContainer(388068805)
+    fun notificationBlur_isNonZero_whenShadeIsExpanded() =
+        testScope.runTest {
+            val values by collectValues(underTest.notificationBlurRadius)
+            kosmos.bouncerWindowBlurTestUtil.shadeExpanded(true)
+            runCurrent()
+
+            kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius(
+                transitionProgress = listOf(0f, 0f, 0.1f, 0.2f, 0.3f, 1f),
+                startValue = kosmos.blurConfig.maxBlurRadiusPx / 3.0f,
+                endValue = kosmos.blurConfig.maxBlurRadiusPx / 3.0f,
+                transitionFactory = ::step,
+                actualValuesProvider = { values },
+                checkInterpolatedValues = false,
+            )
+        }
+
+    @Test
+    @EnableFlags(FLAG_BOUNCER_UI_REVAMP)
+    @BrokenWithSceneContainer(388068805)
+    fun notifications_areFullyVisible_whenShadeIsExpanded() =
+        testScope.runTest {
+            val values by collectValues(underTest.notificationAlpha)
+            kosmos.bouncerWindowBlurTestUtil.shadeExpanded(true)
+            runCurrent()
+
+            kosmos.bouncerWindowBlurTestUtil.assertTransitionToBlurRadius(
+                transitionProgress = listOf(0f, 0f, 0.1f, 0.2f, 0.3f, 1f),
+                startValue = 1.0f,
+                endValue = 1.0f,
+                transitionFactory = ::step,
+                actualValuesProvider = { values },
+                checkInterpolatedValues = false,
+            )
+        }
+
     private fun step(
         value: Float,
         state: TransitionState = TransitionState.RUNNING,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
index fc1d73b..3a3f537 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
@@ -35,6 +35,7 @@
 import android.app.Dialog;
 import android.media.projection.StopReason;
 import android.os.Handler;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.FlagsParameterization;
 import android.service.quicksettings.Tile;
 import android.testing.TestableLooper;
@@ -52,6 +53,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.QsEventLogger;
+import com.android.systemui.qs.flags.QsDetailedView;
 import com.android.systemui.qs.flags.QsInCompose;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
@@ -63,6 +65,7 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -70,11 +73,11 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.List;
-
 import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
 import platform.test.runner.parameterized.Parameters;
 
+import java.util.List;
+
 @RunWith(ParameterizedAndroidJunit4.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @SmallTest
@@ -82,7 +85,8 @@
 
     @Parameters(name = "{0}")
     public static List<FlagsParameterization> getParams() {
-        return allCombinationsOf(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX);
+        return allCombinationsOf(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX,
+                QsDetailedView.FLAG_NAME);
     }
 
     @Mock
@@ -336,6 +340,30 @@
                 .notifyPermissionRequestDisplayed(mContext.getUserId());
     }
 
+    @Test
+    @EnableFlags(QsDetailedView.FLAG_NAME)
+    public void testNotStartingAndRecording_returnDetailsViewModel() {
+        when(mController.isStarting()).thenReturn(false);
+        when(mController.isRecording()).thenReturn(false);
+        mTile.getDetailsViewModel(Assert::assertNotNull);
+    }
+
+    @Test
+    @EnableFlags(QsDetailedView.FLAG_NAME)
+    public void testStarting_notReturnDetailsViewModel() {
+        when(mController.isStarting()).thenReturn(true);
+        when(mController.isRecording()).thenReturn(false);
+        mTile.getDetailsViewModel(Assert::assertNull);
+    }
+
+    @Test
+    @EnableFlags(QsDetailedView.FLAG_NAME)
+    public void testRecording_notReturnDetailsViewModel() {
+        when(mController.isStarting()).thenReturn(false);
+        when(mController.isRecording()).thenReturn(true);
+        mTile.getDetailsViewModel(Assert::assertNull);
+    }
+
     private QSTile.Icon createExpectedIcon(int resId) {
         if (QsInCompose.isEnabled()) {
             return new QSTileImpl.DrawableIconWithRes(mContext.getDrawable(resId), resId);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 789ca51..62c3604 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -85,7 +85,6 @@
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingObserver;
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
@@ -335,16 +334,14 @@
         mFeatureFlags.set(Flags.QS_USER_DETAIL_SHORTCUT, false);
 
         mMainDispatcher = getMainDispatcher();
-        KeyguardInteractorFactory.WithDependencies keyguardInteractorDeps =
-                KeyguardInteractorFactory.create();
-        mFakeKeyguardRepository = keyguardInteractorDeps.getRepository();
+        mFakeKeyguardRepository = mKosmos.getKeyguardRepository();
         mFakeKeyguardClockRepository = new FakeKeyguardClockRepository();
         mKeyguardClockInteractor = mKosmos.getKeyguardClockInteractor();
-        mKeyguardInteractor = keyguardInteractorDeps.getKeyguardInteractor();
+        mKeyguardInteractor = mKosmos.getKeyguardInteractor();
         mShadeRepository = new FakeShadeRepository();
         mShadeAnimationInteractor = new ShadeAnimationInteractorLegacyImpl(
                 new ShadeAnimationRepository(), mShadeRepository);
-        mPowerInteractor = keyguardInteractorDeps.getPowerInteractor();
+        mPowerInteractor = mKosmos.getPowerInteractor();
         when(mKeyguardTransitionInteractor.isInTransitionWhere(any(), any())).thenReturn(
                 MutableStateFlow(false));
         when(mKeyguardTransitionInteractor.isInTransition(any(), any()))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarSignalPolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarSignalPolicyTest.kt
index bb9141a..5f73ac4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarSignalPolicyTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarSignalPolicyTest.kt
@@ -45,8 +45,11 @@
 import org.junit.Before
 import org.junit.runner.RunWith
 import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
 import org.mockito.kotlin.clearInvocations
+import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
 import org.mockito.kotlin.verifyNoMoreInteractions
 
 @SmallTest
@@ -106,10 +109,10 @@
             // Make sure the legacy code path does not change airplane mode when the refactor
             // flag is enabled.
             underTest.setIsAirplaneMode(IconState(true, TelephonyIcons.FLIGHT_MODE_ICON, ""))
-            verifyNoMoreInteractions(statusBarIconController)
+            verify(statusBarIconController, never()).setIconVisibility(eq(slotAirplane), any())
 
             underTest.setIsAirplaneMode(IconState(false, TelephonyIcons.FLIGHT_MODE_ICON, ""))
-            verifyNoMoreInteractions(statusBarIconController)
+            verify(statusBarIconController, never()).setIconVisibility(eq(slotAirplane), any())
         }
 
     @Test
@@ -144,10 +147,10 @@
             // Make sure changing airplane mode from airplaneModeRepository does nothing
             // if the StatusBarSignalPolicyRefactor is not enabled.
             airplaneModeInteractor.setIsAirplaneMode(true)
-            verifyNoMoreInteractions(statusBarIconController)
+            verify(statusBarIconController, never()).setIconVisibility(eq(slotAirplane), any())
 
             airplaneModeInteractor.setIsAirplaneMode(false)
-            verifyNoMoreInteractions(statusBarIconController)
+            verify(statusBarIconController, never()).setIconVisibility(eq(slotAirplane), any())
         }
 
     @Test
@@ -196,7 +199,7 @@
             underTest.setEthernetIndicators(
                 IconState(/* visible= */ true, /* icon= */ 1, /* contentDescription= */ "Ethernet")
             )
-            verifyNoMoreInteractions(statusBarIconController)
+            verify(statusBarIconController, never()).setIconVisibility(eq(slotEthernet), any())
 
             underTest.setEthernetIndicators(
                 IconState(
@@ -205,7 +208,7 @@
                     /* contentDescription= */ "No ethernet",
                 )
             )
-            verifyNoMoreInteractions(statusBarIconController)
+            verify(statusBarIconController, never()).setIconVisibility(eq(slotEthernet), any())
         }
 
     @Test
@@ -217,13 +220,13 @@
             clearInvocations(statusBarIconController)
 
             connectivityRepository.fake.setEthernetConnected(default = true, validated = true)
-            verifyNoMoreInteractions(statusBarIconController)
+            verify(statusBarIconController, never()).setIconVisibility(eq(slotEthernet), any())
 
             connectivityRepository.fake.setEthernetConnected(default = false, validated = false)
-            verifyNoMoreInteractions(statusBarIconController)
+            verify(statusBarIconController, never()).setIconVisibility(eq(slotEthernet), any())
 
             connectivityRepository.fake.setEthernetConnected(default = true, validated = false)
-            verifyNoMoreInteractions(statusBarIconController)
+            verify(statusBarIconController, never()).setIconVisibility(eq(slotEthernet), any())
         }
 
     @Test
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 912633c..e6fbc72 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
@@ -101,7 +101,6 @@
         // VERIFY that the renderer is not queried for group or row controllers
         inOrder(spyViewRenderer).apply {
             verify(spyViewRenderer, times(1)).onRenderList(any())
-            verify(spyViewRenderer, times(1)).getStackController()
             verify(spyViewRenderer, never()).getGroupController(any())
             verify(spyViewRenderer, never()).getRowController(any())
             verify(spyViewRenderer, times(1)).onDispatchComplete()
@@ -121,7 +120,6 @@
         // VERIFY that the renderer is queried once per group/entry
         inOrder(spyViewRenderer).apply {
             verify(spyViewRenderer, times(1)).onRenderList(any())
-            verify(spyViewRenderer, times(1)).getStackController()
             verify(spyViewRenderer, times(2)).getGroupController(any())
             verify(spyViewRenderer, times(8)).getRowController(any())
             verify(spyViewRenderer, times(1)).onDispatchComplete()
@@ -144,7 +142,6 @@
         // VERIFY that the renderer is queried once per group/entry
         inOrder(spyViewRenderer).apply {
             verify(spyViewRenderer, times(1)).onRenderList(any())
-            verify(spyViewRenderer, times(1)).getStackController()
             verify(spyViewRenderer, times(2)).getGroupController(any())
             verify(spyViewRenderer, times(8)).getRowController(any())
             verify(spyViewRenderer, times(1)).onDispatchComplete()
@@ -162,7 +159,7 @@
         onRenderListListener.onRenderList(listWith2Groups8Entries())
 
         // VERIFY that the listeners are invoked once per group and once per entry
-        verify(onAfterRenderListListener, times(1)).onAfterRenderList(any(), any())
+        verify(onAfterRenderListListener, times(1)).onAfterRenderList(any())
         verify(onAfterRenderGroupListener, times(2)).onAfterRenderGroup(any(), any())
         verify(onAfterRenderEntryListener, times(8)).onAfterRenderEntry(any(), any())
         verifyNoMoreInteractions(
@@ -182,7 +179,7 @@
         onRenderListListener.onRenderList(listOf())
 
         // VERIFY that the stack listener is invoked once but other listeners are not
-        verify(onAfterRenderListListener, times(1)).onAfterRenderList(any(), any())
+        verify(onAfterRenderListListener, times(1)).onAfterRenderList(any())
         verify(onAfterRenderGroupListener, never()).onAfterRenderGroup(any(), any())
         verify(onAfterRenderEntryListener, never()).onAfterRenderEntry(any(), any())
         verifyNoMoreInteractions(
@@ -203,8 +200,6 @@
     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()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
index 54ce88b..83c6150 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
@@ -26,7 +26,7 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
-import com.android.systemui.statusbar.notification.collection.render.NotifStats
+import com.android.systemui.statusbar.notification.data.model.NotifStats
 import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
 import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
@@ -275,7 +275,6 @@
 
             activeNotificationListRepository.notifStats.value =
                 NotifStats(
-                    numActiveNotifs = 2,
                     hasNonClearableAlertingNotifs = false,
                     hasClearableAlertingNotifs = true,
                     hasNonClearableSilentNotifs = false,
@@ -293,7 +292,6 @@
 
             activeNotificationListRepository.notifStats.value =
                 NotifStats(
-                    numActiveNotifs = 2,
                     hasNonClearableAlertingNotifs = false,
                     hasClearableAlertingNotifs = false,
                     hasNonClearableSilentNotifs = false,
@@ -311,7 +309,6 @@
 
             activeNotificationListRepository.notifStats.value =
                 NotifStats(
-                    numActiveNotifs = 0,
                     hasNonClearableAlertingNotifs = false,
                     hasClearableAlertingNotifs = false,
                     hasNonClearableSilentNotifs = false,
@@ -329,7 +326,6 @@
 
             activeNotificationListRepository.notifStats.value =
                 NotifStats(
-                    numActiveNotifs = 2,
                     hasNonClearableAlertingNotifs = false,
                     hasClearableAlertingNotifs = false,
                     hasNonClearableSilentNotifs = false,
@@ -347,7 +343,6 @@
 
             activeNotificationListRepository.notifStats.value =
                 NotifStats(
-                    numActiveNotifs = 2,
                     hasNonClearableAlertingNotifs = true,
                     hasClearableAlertingNotifs = false,
                     hasNonClearableSilentNotifs = true,
@@ -365,7 +360,6 @@
 
             activeNotificationListRepository.notifStats.value =
                 NotifStats(
-                    numActiveNotifs = 2,
                     hasNonClearableAlertingNotifs = false,
                     hasClearableAlertingNotifs = true,
                     hasNonClearableSilentNotifs = false,
@@ -383,7 +377,6 @@
 
             activeNotificationListRepository.notifStats.value =
                 NotifStats(
-                    numActiveNotifs = 2,
                     hasNonClearableAlertingNotifs = false,
                     hasClearableAlertingNotifs = false,
                     hasNonClearableSilentNotifs = true,
@@ -401,7 +394,6 @@
 
             activeNotificationListRepository.notifStats.value =
                 NotifStats(
-                    numActiveNotifs = 2,
                     hasNonClearableAlertingNotifs = false,
                     hasClearableAlertingNotifs = false,
                     hasNonClearableSilentNotifs = false,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
index 06b1c43..b3a60b0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
@@ -37,7 +37,7 @@
 import com.android.systemui.res.R
 import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.shared.settings.data.repository.fakeSecureSettingsRepository
-import com.android.systemui.statusbar.notification.collection.render.NotifStats
+import com.android.systemui.statusbar.notification.data.model.NotifStats
 import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
 import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
 import com.android.systemui.statusbar.notification.footer.shared.NotifRedesignFooter
@@ -115,7 +115,6 @@
 
             activeNotificationListRepository.notifStats.value =
                 NotifStats(
-                    numActiveNotifs = 2,
                     hasNonClearableAlertingNotifs = false,
                     hasClearableAlertingNotifs = true,
                     hasNonClearableSilentNotifs = false,
@@ -133,7 +132,6 @@
 
             activeNotificationListRepository.notifStats.value =
                 NotifStats(
-                    numActiveNotifs = 2,
                     hasNonClearableAlertingNotifs = false,
                     hasClearableAlertingNotifs = false,
                     hasNonClearableSilentNotifs = false,
@@ -151,7 +149,6 @@
 
             activeNotificationListRepository.notifStats.value =
                 NotifStats(
-                    numActiveNotifs = 2,
                     hasNonClearableAlertingNotifs = false,
                     hasClearableAlertingNotifs = true,
                     hasNonClearableSilentNotifs = false,
@@ -183,7 +180,6 @@
             // AND there are clearable notifications
             activeNotificationListRepository.notifStats.value =
                 NotifStats(
-                    numActiveNotifs = 2,
                     hasNonClearableAlertingNotifs = false,
                     hasClearableAlertingNotifs = true,
                     hasNonClearableSilentNotifs = false,
@@ -217,7 +213,6 @@
             // AND there are clearable notifications
             activeNotificationListRepository.notifStats.value =
                 NotifStats(
-                    numActiveNotifs = 2,
                     hasNonClearableAlertingNotifs = false,
                     hasClearableAlertingNotifs = true,
                     hasNonClearableSilentNotifs = false,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 45977886..a045b37 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -70,6 +70,7 @@
 import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel.HorizontalPosition
 import com.android.systemui.testKosmos
+import com.android.systemui.window.ui.viewmodel.fakeBouncerTransitions
 import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
 import kotlin.test.assertIs
@@ -1395,6 +1396,19 @@
             assertThat(stackAbsoluteBottom).isEqualTo(100F)
         }
 
+    @Test
+    fun blurRadius_emitsValues_fromPrimaryBouncerTransitions() =
+        testScope.runTest {
+            val blurRadius by collectLastValue(underTest.blurRadius)
+            assertThat(blurRadius).isEqualTo(0.0f)
+
+            kosmos.fakeBouncerTransitions.first().notificationBlurRadius.value = 30.0f
+            assertThat(blurRadius).isEqualTo(30.0f)
+
+            kosmos.fakeBouncerTransitions.last().notificationBlurRadius.value = 40.0f
+            assertThat(blurRadius).isEqualTo(40.0f)
+        }
+
     private suspend fun TestScope.showLockscreen() {
         shadeTestUtil.setQsExpansion(0f)
         shadeTestUtil.setLockscreenShadeExpansion(0f)
diff --git a/packages/SystemUI/proguard_common.flags b/packages/SystemUI/proguard_common.flags
index 162d8ae..02b2bcf 100644
--- a/packages/SystemUI/proguard_common.flags
+++ b/packages/SystemUI/proguard_common.flags
@@ -1,5 +1,11 @@
 -include proguard_kotlin.flags
--keep class com.android.systemui.VendorServices
+
+# VendorServices implements CoreStartable and may be instantiated reflectively in
+# SystemUIApplication#startAdditionalStartable.
+# TODO(b/373579455): Rewrite this to a @UsesReflection keep annotation.
+-keep class com.android.systemui.VendorServices {
+  public void <init>();
+}
 
 # Needed to ensure callback field references are kept in their respective
 # owning classes when the downstream callback registrars only store weak refs.
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index a01ff3d..d445ed9 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3919,6 +3919,16 @@
          The helper is a component that shows the user which keyboard shortcuts they can use.
          [CHAR LIMIT=NONE] -->
     <string name="shortcut_helper_plus_symbol">+</string>
+    <!-- Accessibility label for the plus icon on a shortcut in shortcut helper that allows the user
+         to add a new custom shortcut.
+         The helper is a component that shows the user which keyboard shortcuts they can use.
+         [CHAR LIMIT=NONE] -->
+    <string name="shortcut_helper_add_shortcut_button_label">Add shortcut</string>
+    <!-- Accessibility label for the bin(trash) icon on a shortcut in shortcut helper that allows the
+         user to delete an existing custom shortcut.
+         The helper is a component that shows the user which keyboard shortcuts they can use.
+         [CHAR LIMIT=NONE] -->
+    <string name="shortcut_helper_delete_shortcut_button_label">Delete shortcut</string>
 
     <!-- Keyboard touchpad tutorial scheduler-->
     <!-- Notification title for launching keyboard tutorial [CHAR_LIMIT=100] -->
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 274fa59..a16b4a6 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
@@ -53,11 +53,11 @@
 
     override suspend fun onActivated(): Nothing {
         viewModel.shortcutCustomizationUiState.collect { uiState ->
-            when(uiState){
+            when (uiState) {
                 is AddShortcutDialog,
                 is DeleteShortcutDialog,
                 is ResetShortcutDialog -> {
-                    if (dialog == null){
+                    if (dialog == null) {
                         dialog = createDialog().also { it.show() }
                     }
                 }
@@ -85,7 +85,9 @@
             ShortcutCustomizationDialog(
                 uiState = uiState,
                 modifier = Modifier.width(364.dp).wrapContentHeight().padding(vertical = 24.dp),
-                onKeyPress = { viewModel.onKeyPressed(it) },
+                onShortcutKeyCombinationSelected = {
+                    viewModel.onShortcutKeyCombinationSelected(it)
+                },
                 onCancel = { dialog.dismiss() },
                 onConfirmSetShortcut = { coroutineScope.launch { viewModel.onSetShortcut() } },
                 onConfirmDeleteShortcut = {
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
index 3819f6d..d9e55f8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
@@ -49,8 +49,12 @@
 import androidx.compose.ui.focus.focusProperties
 import androidx.compose.ui.focus.focusRequester
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.key.Key
 import androidx.compose.ui.input.key.KeyEvent
-import androidx.compose.ui.input.key.onKeyEvent
+import androidx.compose.ui.input.key.KeyEventType
+import androidx.compose.ui.input.key.key
+import androidx.compose.ui.input.key.onPreviewKeyEvent
+import androidx.compose.ui.input.key.type
 import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.font.FontWeight
@@ -65,7 +69,7 @@
 fun ShortcutCustomizationDialog(
     uiState: ShortcutCustomizationUiState,
     modifier: Modifier = Modifier,
-    onKeyPress: (KeyEvent) -> Boolean,
+    onShortcutKeyCombinationSelected: (KeyEvent) -> Boolean,
     onCancel: () -> Unit,
     onConfirmSetShortcut: () -> Unit,
     onConfirmDeleteShortcut: () -> Unit,
@@ -73,7 +77,13 @@
 ) {
     when (uiState) {
         is ShortcutCustomizationUiState.AddShortcutDialog -> {
-            AddShortcutDialog(modifier, uiState, onKeyPress, onCancel, onConfirmSetShortcut)
+            AddShortcutDialog(
+                modifier,
+                uiState,
+                onShortcutKeyCombinationSelected,
+                onCancel,
+                onConfirmSetShortcut,
+            )
         }
         is ShortcutCustomizationUiState.DeleteShortcutDialog -> {
             DeleteShortcutDialog(modifier, onCancel, onConfirmDeleteShortcut)
@@ -91,29 +101,27 @@
 private fun AddShortcutDialog(
     modifier: Modifier,
     uiState: ShortcutCustomizationUiState.AddShortcutDialog,
-    onKeyPress: (KeyEvent) -> Boolean,
+    onShortcutKeyCombinationSelected: (KeyEvent) -> Boolean,
     onCancel: () -> Unit,
-    onConfirmSetShortcut: () -> Unit
-){
+    onConfirmSetShortcut: () -> Unit,
+) {
     Column(modifier = modifier) {
         Title(uiState.shortcutLabel)
         Description(
-            text =
-            stringResource(
-                id = R.string.shortcut_customize_mode_add_shortcut_description
-            )
+            text = stringResource(id = R.string.shortcut_customize_mode_add_shortcut_description)
         )
         PromptShortcutModifier(
             modifier =
-            Modifier.padding(top = 24.dp, start = 116.5.dp, end = 116.5.dp)
-                .width(131.dp)
-                .height(48.dp),
+                Modifier.padding(top = 24.dp, start = 116.5.dp, end = 116.5.dp)
+                    .width(131.dp)
+                    .height(48.dp),
             defaultModifierKey = uiState.defaultCustomShortcutModifierKey,
         )
         SelectedKeyCombinationContainer(
             shouldShowError = uiState.errorMessage.isNotEmpty(),
-            onKeyPress = onKeyPress,
+            onShortcutKeyCombinationSelected = onShortcutKeyCombinationSelected,
             pressedKeys = uiState.pressedKeys,
+            onConfirmSetShortcut = onConfirmSetShortcut,
         )
         ErrorMessageContainer(uiState.errorMessage)
         DialogButtons(
@@ -121,9 +129,7 @@
             isConfirmButtonEnabled = uiState.pressedKeys.isNotEmpty(),
             onConfirm = onConfirmSetShortcut,
             confirmButtonText =
-            stringResource(
-                R.string.shortcut_helper_customize_dialog_set_shortcut_button_label
-            ),
+                stringResource(R.string.shortcut_helper_customize_dialog_set_shortcut_button_label),
         )
     }
 }
@@ -132,20 +138,15 @@
 private fun DeleteShortcutDialog(
     modifier: Modifier,
     onCancel: () -> Unit,
-    onConfirmDeleteShortcut: () -> Unit
-){
+    onConfirmDeleteShortcut: () -> Unit,
+) {
     ConfirmationDialog(
         modifier = modifier,
-        title =
-        stringResource(
-            id = R.string.shortcut_customize_mode_remove_shortcut_dialog_title
-        ),
+        title = stringResource(id = R.string.shortcut_customize_mode_remove_shortcut_dialog_title),
         description =
-        stringResource(
-            id = R.string.shortcut_customize_mode_remove_shortcut_description
-        ),
+            stringResource(id = R.string.shortcut_customize_mode_remove_shortcut_description),
         confirmButtonText =
-        stringResource(R.string.shortcut_helper_customize_dialog_remove_button_label),
+            stringResource(R.string.shortcut_helper_customize_dialog_remove_button_label),
         onCancel = onCancel,
         onConfirm = onConfirmDeleteShortcut,
     )
@@ -155,20 +156,15 @@
 private fun ResetShortcutDialog(
     modifier: Modifier,
     onCancel: () -> Unit,
-    onConfirmResetShortcut: () -> Unit
-){
+    onConfirmResetShortcut: () -> Unit,
+) {
     ConfirmationDialog(
         modifier = modifier,
-        title =
-        stringResource(
-            id = R.string.shortcut_customize_mode_reset_shortcut_dialog_title
-        ),
+        title = stringResource(id = R.string.shortcut_customize_mode_reset_shortcut_dialog_title),
         description =
-        stringResource(
-            id = R.string.shortcut_customize_mode_reset_shortcut_description
-        ),
+            stringResource(id = R.string.shortcut_customize_mode_reset_shortcut_description),
         confirmButtonText =
-        stringResource(R.string.shortcut_helper_customize_dialog_reset_button_label),
+            stringResource(R.string.shortcut_helper_customize_dialog_reset_button_label),
         onCancel = onCancel,
         onConfirm = onConfirmResetShortcut,
     )
@@ -201,6 +197,9 @@
     onConfirm: () -> Unit,
     confirmButtonText: String,
 ) {
+    val focusRequester = remember { FocusRequester() }
+    LaunchedEffect(Unit) { focusRequester.requestFocus() }
+
     Row(
         modifier =
             Modifier.padding(top = 24.dp, start = 24.dp, end = 24.dp)
@@ -218,6 +217,10 @@
         )
         Spacer(modifier = Modifier.width(8.dp))
         ShortcutHelperButton(
+            modifier =
+                Modifier.focusRequester(focusRequester).focusProperties {
+                    canFocus = true
+                }, // enable focus on touch/click mode
             onClick = onConfirm,
             color = MaterialTheme.colorScheme.primary,
             width = 116.dp,
@@ -248,8 +251,9 @@
 @Composable
 private fun SelectedKeyCombinationContainer(
     shouldShowError: Boolean,
-    onKeyPress: (KeyEvent) -> Boolean,
+    onShortcutKeyCombinationSelected: (KeyEvent) -> Boolean,
     pressedKeys: List<ShortcutKey>,
+    onConfirmSetShortcut: () -> Unit,
 ) {
     val interactionSource = remember { MutableInteractionSource() }
     val isFocused by interactionSource.collectIsFocusedAsState()
@@ -269,7 +273,17 @@
             Modifier.padding(all = 16.dp)
                 .sizeIn(minWidth = 332.dp, minHeight = 56.dp)
                 .border(width = 2.dp, color = outlineColor, shape = RoundedCornerShape(50.dp))
-                .onKeyEvent { onKeyPress(it) }
+                .onPreviewKeyEvent { keyEvent ->
+                    val keyEventProcessed = onShortcutKeyCombinationSelected(keyEvent)
+                    if (
+                        !keyEventProcessed &&
+                            keyEvent.key == Key.Enter &&
+                            keyEvent.type == KeyEventType.KeyUp
+                    ) {
+                        onConfirmSetShortcut()
+                        true
+                    } else keyEventProcessed
+                }
                 .focusProperties { canFocus = true } // enables keyboard focus when in touch mode
                 .focusRequester(focusRequester),
         interactionSource = interactionSource,
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 aea583d..ba31d08 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
@@ -729,6 +729,7 @@
         contentColor = MaterialTheme.colorScheme.primary,
         contentPaddingVertical = 0.dp,
         contentPaddingHorizontal = 0.dp,
+        contentDescription = stringResource(R.string.shortcut_helper_add_shortcut_button_label),
     )
 }
 
@@ -749,6 +750,7 @@
         contentColor = MaterialTheme.colorScheme.primary,
         contentPaddingVertical = 0.dp,
         contentPaddingHorizontal = 0.dp,
+        contentDescription = stringResource(R.string.shortcut_helper_delete_shortcut_button_label),
     )
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
index 55c0fe2..9a380f4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
@@ -230,6 +230,7 @@
     contentPaddingVertical: Dp = 10.dp,
     enabled: Boolean = true,
     border: BorderStroke? = null,
+    contentDescription: String? = null,
 ) {
     ShortcutHelperButtonSurface(
         onClick = onClick,
@@ -254,8 +255,7 @@
                 Icon(
                     tint = contentColor,
                     imageVector = iconSource.imageVector,
-                    contentDescription =
-                        null, // TODO this probably should not be null for accessibility.
+                    contentDescription = contentDescription,
                     modifier = Modifier.size(20.dp).wrapContentSize(Alignment.Center),
                 )
             }
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 373eb25..915a66c 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
@@ -46,6 +46,7 @@
     private val context: Context,
     private val shortcutCustomizationInteractor: ShortcutCustomizationInteractor,
 ) {
+    private var keyDownEventCache: KeyEvent? = null
     private val _shortcutCustomizationUiState =
         MutableStateFlow<ShortcutCustomizationUiState>(ShortcutCustomizationUiState.Inactive)
 
@@ -94,9 +95,16 @@
         shortcutCustomizationInteractor.updateUserSelectedKeyCombination(null)
     }
 
-    fun onKeyPressed(keyEvent: KeyEvent): Boolean {
-        if ((keyEvent.isMetaPressed && keyEvent.type == KeyEventType.KeyDown)) {
-            updatePressedKeys(keyEvent)
+    fun onShortcutKeyCombinationSelected(keyEvent: KeyEvent): Boolean {
+        if (isModifier(keyEvent)) {
+            return false
+        }
+        if (keyEvent.isMetaPressed && keyEvent.type == KeyEventType.KeyDown) {
+            keyDownEventCache = keyEvent
+            return true
+        } else if (keyEvent.type == KeyEventType.KeyUp && keyEvent.key == keyDownEventCache?.key) {
+            updatePressedKeys(keyDownEventCache!!)
+            clearKeyDownEventCache()
             return true
         }
         return false
@@ -157,16 +165,21 @@
         return (uiState as? AddShortcutDialog)?.copy(errorMessage = errorMessage) ?: uiState
     }
 
+    private fun isModifier(keyEvent: KeyEvent) = SUPPORTED_MODIFIERS.contains(keyEvent.key)
+
     private fun updatePressedKeys(keyEvent: KeyEvent) {
-        val isModifier = SUPPORTED_MODIFIERS.contains(keyEvent.key)
         val keyCombination =
             KeyCombination(
                 modifiers = keyEvent.nativeKeyEvent.modifiers,
-                keyCode = if (!isModifier) keyEvent.key.nativeKeyCode else null,
+                keyCode = if (!isModifier(keyEvent)) keyEvent.key.nativeKeyCode else null,
             )
         shortcutCustomizationInteractor.updateUserSelectedKeyCombination(keyCombination)
     }
 
+    private fun clearKeyDownEventCache() {
+        keyDownEventCache = null
+    }
+
     @AssistedFactory
     interface Factory {
         fun create(): ShortcutCustomizationViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 9896365..b42da52 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -132,6 +132,8 @@
                             if (SceneContainerFlag.isEnabled) return@collect
                             startTransitionTo(
                                 toState = KeyguardState.GONE,
+                                modeOnCanceled = TransitionModeOnCanceled.REVERSE,
+                                ownerReason = "canWakeDirectlyToGone = true",
                             )
                         } else if (shouldTransitionToLockscreen) {
                             val modeOnCanceled =
@@ -146,7 +148,7 @@
                             startTransitionTo(
                                 toState = KeyguardState.LOCKSCREEN,
                                 modeOnCanceled = modeOnCanceled,
-                                ownerReason = "listen for aod to awake"
+                                ownerReason = "listen for aod to awake",
                             )
                         } else if (shouldTransitionToOccluded) {
                             startTransitionTo(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index fbe31bb..8f7f2a0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -44,7 +44,6 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
 import com.android.systemui.keyguard.shared.model.StatusBarState
-import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
@@ -84,7 +83,6 @@
 @Inject
 constructor(
     private val repository: KeyguardRepository,
-    powerInteractor: PowerInteractor,
     bouncerRepository: KeyguardBouncerRepository,
     @ShadeDisplayAware configurationInteractor: ConfigurationInteractor,
     shadeRepository: ShadeRepository,
@@ -216,11 +214,7 @@
                     // should actually be quite strange to leave AOD and then go straight to
                     // DREAMING so this should be fine.
                     delay(IS_ABLE_TO_DREAM_DELAY_MS)
-                    isDreaming
-                        .sample(powerInteractor.isAwake) { isDreaming, isAwake ->
-                            isDreaming && isAwake
-                        }
-                        .debounce(50L)
+                    isDreaming.debounce(50L)
                 } else {
                     flowOf(false)
                 }
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 a133f06..3bdc32d 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
@@ -116,9 +116,10 @@
      * - We're wake and unlocking (fingerprint auth occurred while asleep).
      * - We're allowed to ignore auth and return to GONE, due to timeouts not elapsing.
      * - We're DREAMING and dismissible.
-     * - We're already GONE. Technically you're already awake when GONE, but this makes it easier to
-     *   reason about this state (for example, if canWakeDirectlyToGone, don't tell WM to pause the
-     *   top activity - something you should never do while GONE as well).
+     * - We're already GONE and not transitioning out of GONE. Technically you're already awake when
+     *   GONE, but this makes it easier to reason about this state (for example, if
+     *   canWakeDirectlyToGone, don't tell WM to pause the top activity - something you should never
+     *   do while GONE as well).
      */
     val canWakeDirectlyToGone =
         combine(
@@ -138,7 +139,8 @@
                     canIgnoreAuthAndReturnToGone ||
                     (currentState == KeyguardState.DREAMING &&
                         keyguardInteractor.isKeyguardDismissible.value) ||
-                    currentState == KeyguardState.GONE
+                    (currentState == KeyguardState.GONE &&
+                        transitionInteractor.getStartedState() == KeyguardState.GONE)
             }
             .distinctUntilChanged()
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/BlurConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/BlurConfig.kt
index 542fb9b..3eb8522 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/BlurConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/BlurConfig.kt
@@ -23,4 +23,10 @@
     // No-op config that will be used by dagger of other SysUI variants which don't blur the
     // background surface.
     @Inject constructor() : this(0.0f, 0.0f)
+
+    companion object {
+        // Blur the shade much lesser than the background surface so that the surface is
+        // distinguishable from the background.
+        @JvmStatic fun Float.maxBlurRadiusToNotificationPanelBlurRadius(): Float = this / 3.0f
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/PrimaryBouncerTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/PrimaryBouncerTransition.kt
index e77e9dd..eb1afb4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/PrimaryBouncerTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/transitions/PrimaryBouncerTransition.kt
@@ -30,6 +30,9 @@
     /** Radius of blur applied to the window's root view. */
     val windowBlurRadius: Flow<Float>
 
+    /** Radius of blur applied to the notifications on expanded shade */
+    val notificationBlurRadius: Flow<Float>
+
     fun transitionProgressToBlurRadius(
         starBlurRadius: Float,
         endBlurRadius: Float,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt
index f174557..92bb5e6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import com.android.systemui.Flags
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor
 import com.android.systemui.keyguard.shared.model.Edge
@@ -23,6 +24,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.BlurConfig
+import com.android.systemui.keyguard.ui.transitions.BlurConfig.Companion.maxBlurRadiusToNotificationPanelBlurRadius
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
@@ -73,7 +75,28 @@
 
     val lockscreenAlpha: Flow<Float> = if (WindowBlurFlag.isEnabled) alphaFlow else emptyFlow()
 
-    val notificationAlpha: Flow<Float> = alphaFlow
+    val notificationAlpha: Flow<Float> =
+        if (Flags.bouncerUiRevamp()) {
+            shadeDependentFlows.transitionFlow(
+                flowWhenShadeIsNotExpanded = lockscreenAlpha,
+                flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(1f),
+            )
+        } else {
+            alphaFlow
+        }
+
+    override val notificationBlurRadius: Flow<Float> =
+        if (Flags.bouncerUiRevamp()) {
+            shadeDependentFlows.transitionFlow(
+                flowWhenShadeIsNotExpanded = emptyFlow(),
+                flowWhenShadeIsExpanded =
+                    transitionAnimation.immediatelyTransitionTo(
+                        blurConfig.maxBlurRadiusPx.maxBlurRadiusToNotificationPanelBlurRadius()
+                    ),
+            )
+        } else {
+            emptyFlow<Float>()
+        }
 
     override val deviceEntryParentViewAlpha: Flow<Float> =
         transitionAnimation.immediatelyTransitionTo(0f)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt
index dbb6a49..e3b5587 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt
@@ -53,4 +53,7 @@
 
     override val windowBlurRadius: Flow<Float> =
         transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx)
+
+    override val notificationBlurRadius: Flow<Float> =
+        transitionAnimation.immediatelyTransitionTo(0.0f)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt
index d8b617a..c937d5c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt
@@ -64,4 +64,6 @@
             },
             onFinish = { blurConfig.maxBlurRadiusPx },
         )
+    override val notificationBlurRadius: Flow<Float> =
+        transitionAnimation.immediatelyTransitionTo(0f)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModel.kt
index 597df15..5ab4583 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToPrimaryBouncerTransitionViewModel.kt
@@ -42,4 +42,7 @@
 
     override val windowBlurRadius: Flow<Float> =
         transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx)
+
+    override val notificationBlurRadius: Flow<Float> =
+        transitionAnimation.immediatelyTransitionTo(0.0f)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
index c373fd0..44c4c87 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import com.android.systemui.Flags
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
 import com.android.systemui.keyguard.shared.model.Edge
@@ -23,6 +24,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.keyguard.ui.transitions.BlurConfig
+import com.android.systemui.keyguard.ui.transitions.BlurConfig.Companion.maxBlurRadiusToNotificationPanelBlurRadius
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
@@ -32,6 +34,7 @@
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
 
 /**
  * Breaks down LOCKSCREEN->PRIMARY BOUNCER transition into discrete steps for corresponding views to
@@ -70,6 +73,29 @@
 
     val lockscreenAlpha: Flow<Float> = shortcutsAlpha
 
+    val notificationAlpha: Flow<Float> =
+        if (Flags.bouncerUiRevamp()) {
+            shadeDependentFlows.transitionFlow(
+                flowWhenShadeIsNotExpanded = lockscreenAlpha,
+                flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(1f),
+            )
+        } else {
+            lockscreenAlpha
+        }
+
+    override val notificationBlurRadius: Flow<Float> =
+        if (Flags.bouncerUiRevamp()) {
+            shadeDependentFlows.transitionFlow(
+                flowWhenShadeIsNotExpanded = emptyFlow(),
+                flowWhenShadeIsExpanded =
+                    transitionAnimation.immediatelyTransitionTo(
+                        blurConfig.maxBlurRadiusPx.maxBlurRadiusToNotificationPanelBlurRadius()
+                    ),
+            )
+        } else {
+            emptyFlow()
+        }
+
     override val deviceEntryParentViewAlpha: Flow<Float> =
         shadeDependentFlows.transitionFlow(
             flowWhenShadeIsNotExpanded =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModel.kt
index 4459810..4d3e272 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToPrimaryBouncerTransitionViewModel.kt
@@ -42,4 +42,7 @@
 
     override val windowBlurRadius: Flow<Float> =
         transitionAnimation.immediatelyTransitionTo(blurConfig.maxBlurRadiusPx)
+
+    override val notificationBlurRadius: Flow<Float> =
+        transitionAnimation.immediatelyTransitionTo(0.0f)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt
index fab8008..224191b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt
@@ -91,4 +91,7 @@
             },
             onFinish = { blurConfig.minBlurRadiusPx },
         )
+
+    override val notificationBlurRadius: Flow<Float> =
+        transitionAnimation.immediatelyTransitionTo(0.0f)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModel.kt
index eebdf2e..0f8495f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModel.kt
@@ -80,4 +80,6 @@
             },
             onFinish = { blurConfig.minBlurRadiusPx },
         )
+    override val notificationBlurRadius: Flow<Float> =
+        transitionAnimation.immediatelyTransitionTo(0.0f)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModel.kt
index 3636b74..a13eef2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGlanceableHubTransitionViewModel.kt
@@ -43,4 +43,7 @@
 
     override val windowBlurRadius: Flow<Float> =
         transitionAnimation.immediatelyTransitionTo(blurConfig.minBlurRadiusPx)
+
+    override val notificationBlurRadius: Flow<Float> =
+        transitionAnimation.immediatelyTransitionTo(0.0f)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
index 4ed3e6c..d1233f2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
@@ -166,6 +166,9 @@
             createBouncerWindowBlurFlow(primaryBouncerInteractor::willRunDismissFromKeyguard)
         }
 
+    override val notificationBlurRadius: Flow<Float> =
+        transitionAnimation.immediatelyTransitionTo(0.0f)
+
     val scrimAlpha: Flow<ScrimAlpha> =
         bouncerToGoneFlows.scrimAlpha(TO_GONE_DURATION, PRIMARY_BOUNCER)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt
index 2edc93cb..c53a408 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt
@@ -91,4 +91,7 @@
                     },
                 ),
         )
+
+    override val notificationBlurRadius: Flow<Float> =
+        transitionAnimation.immediatelyTransitionTo(0.0f)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModel.kt
index 3a54a26..fe1708e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToOccludedTransitionViewModel.kt
@@ -42,4 +42,7 @@
 
     override val windowBlurRadius: Flow<Float> =
         transitionAnimation.immediatelyTransitionTo(blurConfig.minBlurRadiusPx)
+
+    override val notificationBlurRadius: Flow<Float> =
+        transitionAnimation.immediatelyTransitionTo(0.0f)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
index ec8d30b..e93cec8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -41,12 +41,14 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.qs.QSTile;
+import com.android.systemui.plugins.qs.TileDetailsViewModel;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.qs.tiles.dialog.ScreenRecordDetailsViewModel;
 import com.android.systemui.res.R;
 import com.android.systemui.screenrecord.RecordingController;
 import com.android.systemui.screenrecord.data.model.ScreenRecordModel;
@@ -54,6 +56,8 @@
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
+import java.util.function.Consumer;
+
 import javax.inject.Inject;
 
 /**
@@ -122,17 +126,78 @@
 
     @Override
     protected void handleClick(@Nullable Expandable expandable) {
+        handleClick(() -> showDialog(expandable));
+    }
+
+    private void showDialog(@Nullable Expandable expandable) {
+        final Dialog dialog = mController.createScreenRecordDialog(
+                this::onStartRecordingClicked);
+
+        executeWhenUnlockedKeyguard(() -> {
+            // We animate from the touched view only if we are not on the keyguard, given that if we
+            // are we will dismiss it which will also collapse the shade.
+            boolean shouldAnimateFromExpandable =
+                    expandable != null && !mKeyguardStateController.isShowing();
+
+            if (shouldAnimateFromExpandable) {
+                DialogTransitionAnimator.Controller controller =
+                        expandable.dialogTransitionController(new DialogCuj(
+                                InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+                                INTERACTION_JANK_TAG));
+                if (controller != null) {
+                    mDialogTransitionAnimator.show(dialog,
+                            controller, /* animateBackgroundBoundsChange= */ true);
+                } else {
+                    dialog.show();
+                }
+            } else {
+                dialog.show();
+            }
+        });
+    }
+
+    private void onStartRecordingClicked() {
+        // We dismiss the shade. Since starting the recording will also dismiss the dialog (if
+        // there is one showing), we disable the exit animation which looks weird when it happens
+        // at the same time as the shade collapsing.
+        mDialogTransitionAnimator.disableAllCurrentDialogsExitAnimations();
+        mPanelInteractor.collapsePanels();
+    }
+
+    private void executeWhenUnlockedKeyguard(Runnable dismissActionCallback) {
+        ActivityStarter.OnDismissAction dismissAction = () -> {
+            dismissActionCallback.run();
+
+            int uid = mUserContextProvider.getUserContext().getUserId();
+            mMediaProjectionMetricsLogger.notifyPermissionRequestDisplayed(uid);
+
+            return false;
+        };
+
+        mKeyguardDismissUtil.executeWhenUnlocked(dismissAction, false /* requiresShadeOpen */,
+                true /* afterKeyguardDone */);
+    }
+
+    private void handleClick(Runnable showPromptCallback) {
         if (mController.isStarting()) {
             cancelCountdown();
         } else if (mController.isRecording()) {
             stopRecording();
         } else {
-            mUiHandler.post(() -> showPrompt(expandable));
+            mUiHandler.post(showPromptCallback);
         }
         refreshState();
     }
 
     @Override
+    public boolean getDetailsViewModel(Consumer<TileDetailsViewModel> callback) {
+        handleClick(() ->
+                callback.accept(new ScreenRecordDetailsViewModel())
+        );
+        return true;
+    }
+
+    @Override
     protected void handleUpdateState(BooleanState state, Object arg) {
         boolean isStarting = mController.isStarting();
         boolean isRecording = mController.isRecording();
@@ -178,49 +243,6 @@
         return mContext.getString(R.string.quick_settings_screen_record_label);
     }
 
-    private void showPrompt(@Nullable Expandable expandable) {
-        // We animate from the touched view only if we are not on the keyguard, given that if we
-        // are we will dismiss it which will also collapse the shade.
-        boolean shouldAnimateFromExpandable =
-                expandable != null && !mKeyguardStateController.isShowing();
-
-        // Create the recording dialog that will collapse the shade only if we start the recording.
-        Runnable onStartRecordingClicked = () -> {
-            // We dismiss the shade. Since starting the recording will also dismiss the dialog, we
-            // disable the exit animation which looks weird when it happens at the same time as the
-            // shade collapsing.
-            mDialogTransitionAnimator.disableAllCurrentDialogsExitAnimations();
-            mPanelInteractor.collapsePanels();
-        };
-
-        final Dialog dialog = mController.createScreenRecordDialog(onStartRecordingClicked);
-
-        ActivityStarter.OnDismissAction dismissAction = () -> {
-            if (shouldAnimateFromExpandable) {
-                DialogTransitionAnimator.Controller controller =
-                        expandable.dialogTransitionController(new DialogCuj(
-                                InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
-                                INTERACTION_JANK_TAG));
-                if (controller != null) {
-                    mDialogTransitionAnimator.show(dialog,
-                            controller, /* animateBackgroundBoundsChange= */ true);
-                } else {
-                    dialog.show();
-                }
-            } else {
-                dialog.show();
-            }
-
-            int uid = mUserContextProvider.getUserContext().getUserId();
-            mMediaProjectionMetricsLogger.notifyPermissionRequestDisplayed(uid);
-
-            return false;
-        };
-
-        mKeyguardDismissUtil.executeWhenUnlocked(dismissAction, false /* requiresShadeOpen */,
-                true /* afterKeyguardDone */);
-    }
-
     private void cancelCountdown() {
         Log.d(TAG, "Cancelling countdown");
         mController.cancelCountdown();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ScreenRecordDetailsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ScreenRecordDetailsViewModel.kt
new file mode 100644
index 0000000..42cb124
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ScreenRecordDetailsViewModel.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.dialog
+
+import android.view.LayoutInflater
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.viewinterop.AndroidView
+import com.android.systemui.plugins.qs.TileDetailsViewModel
+import com.android.systemui.res.R
+
+/** The view model used for the screen record details view in the Quick Settings */
+class ScreenRecordDetailsViewModel() : TileDetailsViewModel() {
+    @Composable
+    override fun GetContentView() {
+        // TODO(b/378514312): Finish implementing this function.
+        AndroidView(
+            modifier = Modifier.fillMaxWidth().heightIn(max = VIEW_MAX_HEIGHT),
+            factory = { context ->
+                // Inflate with the existing dialog xml layout
+                LayoutInflater.from(context).inflate(R.layout.screen_share_dialog, null)
+            },
+        )
+    }
+
+    override fun clickOnSettingsButton() {
+        // No settings button in this tile.
+    }
+
+    override fun getTitle(): String {
+        // TODO(b/388321032): Replace this string with a string in a translatable xml file,
+        return "Screen recording"
+    }
+
+    override fun getSubTitle(): String {
+        // No sub-title in this tile.
+        return ""
+    }
+
+    companion object {
+        private val VIEW_MAX_HEIGHT: Dp = 320.dp
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 1915217..c4306d3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -109,6 +109,7 @@
 import com.android.systemui.keyguard.shared.model.TransitionState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
 import com.android.systemui.keyguard.ui.binder.KeyguardLongPressViewBinder;
+import com.android.systemui.keyguard.ui.transitions.BlurConfig;
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardTouchHandlingViewModel;
 import com.android.systemui.media.controls.domain.pipeline.MediaDataManager;
@@ -914,13 +915,12 @@
         if (!com.android.systemui.Flags.bouncerUiRevamp()) return;
 
         if (isBouncerShowing && isExpanded()) {
-            // Blur the shade much lesser than the background surface so that the surface is
-            // distinguishable from the background.
-            float shadeBlurEffect = mDepthController.getMaxBlurRadiusPx() / 3;
+            float shadeBlurEffect = BlurConfig.maxBlurRadiusToNotificationPanelBlurRadius(
+                    mDepthController.getMaxBlurRadiusPx());
             mView.setRenderEffect(RenderEffect.createBlurEffect(
                     shadeBlurEffect,
                     shadeBlurEffect,
-                    Shader.TileMode.MIRROR));
+                    Shader.TileMode.CLAMP));
         } else {
             mView.setRenderEffect(null);
         }
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 90212ed..034a4fd 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
@@ -36,7 +36,7 @@
 internal constructor(private val notifLiveDataStoreImpl: NotifLiveDataStoreImpl) : CoreCoordinator {
 
     override fun attach(pipeline: NotifPipeline) {
-        pipeline.addOnAfterRenderListListener { entries, _ -> onAfterRenderList(entries) }
+        pipeline.addOnAfterRenderListListener { entries -> onAfterRenderList(entries) }
     }
 
     override fun dumpPipeline(d: PipelineDumper) {
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 d4d3cdf..1cb2366 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
@@ -23,8 +23,7 @@
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl
-import com.android.systemui.statusbar.notification.collection.render.NotifStackController
-import com.android.systemui.statusbar.notification.collection.render.NotifStats
+import com.android.systemui.statusbar.notification.data.model.NotifStats
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor
 import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
@@ -51,8 +50,7 @@
         groupExpansionManagerImpl.attach(pipeline)
     }
 
-    // TODO: b/293167744 - Remove controller param.
-    private fun onAfterRenderList(entries: List<ListEntry>, controller: NotifStackController) =
+    private fun onAfterRenderList(entries: List<ListEntry>) =
         traceSection("StackCoordinator.onAfterRenderList") {
             val notifStats = calculateNotifStats(entries)
             activeNotificationsInteractor.setNotifStats(notifStats)
@@ -84,7 +82,6 @@
             }
         }
         return NotifStats(
-            numActiveNotifs = entries.size,
             hasNonClearableAlertingNotifs = hasNonClearableAlertingNotifs,
             hasClearableAlertingNotifs = hasClearableAlertingNotifs,
             hasNonClearableSilentNotifs = hasNonClearableSilentNotifs,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
index a34d033..c58b3fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
@@ -33,7 +33,6 @@
 import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer;
 import com.android.systemui.statusbar.notification.collection.coordinator.NotifCoordinators;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
-import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
 import com.android.systemui.statusbar.notification.collection.render.RenderStageManager;
 import com.android.systemui.statusbar.notification.collection.render.ShadeViewManager;
 import com.android.systemui.statusbar.notification.collection.render.ShadeViewManagerFactory;
@@ -89,8 +88,7 @@
     public void initialize(
             NotificationListener notificationService,
             NotificationRowBinderImpl rowBinder,
-            NotificationListContainer listContainer,
-            NotifStackController stackController) {
+            NotificationListContainer listContainer) {
         mDumpManager.registerDumpable("NotifPipeline", this);
 
         mNotificationService = notificationService;
@@ -102,7 +100,7 @@
         mNotifPluggableCoordinators.attach(mPipelineWrapper);
 
         // Wire up pipeline
-        mShadeViewManager = mShadeViewManagerFactory.create(listContainer, stackController);
+        mShadeViewManager = mShadeViewManagerFactory.create(listContainer);
         mShadeViewManager.attach(mRenderStageManager);
         mRenderStageManager.attach(mListBuilder);
         mListBuilder.attach(mNotifCollection);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnAfterRenderListListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnAfterRenderListListener.java
index b5a0f7a..ac450c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnAfterRenderListListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnAfterRenderListListener.java
@@ -20,7 +20,6 @@
 
 import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
 
 import java.util.List;
 
@@ -31,9 +30,6 @@
      *
      * @param entries The current list of top-level entries. Note that this is a live view into the
      * current list and will change whenever the pipeline is rerun.
-     * @param controller An object for setting state on the shade.
      */
-    void onAfterRenderList(
-            @NonNull List<ListEntry> entries,
-            @NonNull NotifStackController controller);
+    void onAfterRenderList(@NonNull List<ListEntry> entries);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt
deleted file mode 100644
index a37937a..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.render
-
-import javax.inject.Inject
-
-/** An interface by which the pipeline can make updates to the notification root view. */
-interface NotifStackController {
-    /** Provides stats about the list of notifications attached to the shade */
-    fun setNotifStats(stats: NotifStats)
-}
-
-/** Data provided to the NotificationRootController whenever the pipeline runs */
-data class NotifStats(
-    // TODO(b/293167744): The count can be removed from here when we remove the FooterView flag.
-    val numActiveNotifs: Int,
-    val hasNonClearableAlertingNotifs: Boolean,
-    val hasClearableAlertingNotifs: Boolean,
-    val hasNonClearableSilentNotifs: Boolean,
-    val hasClearableSilentNotifs: Boolean
-) {
-    companion object {
-        @JvmStatic val empty = NotifStats(0, false, false, false, false)
-    }
-}
-
-/**
- * An implementation of NotifStackController which provides default, no-op implementations of each
- * method. This is used by ArcSystemUI so that that implementation can opt-in to overriding methods,
- * rather than forcing us to add no-op implementations in their implementation every time a method
- * is added.
- */
-open class DefaultNotifStackController @Inject constructor() : NotifStackController {
-    override fun setNotifStats(stats: NotifStats) {}
-}
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 410b78b..8284022 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
@@ -37,12 +37,6 @@
     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.
-     */
-    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].
      */
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 9d3b098..21e6837 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
@@ -50,7 +50,7 @@
         traceSection("RenderStageManager.onRenderList") {
             val viewRenderer = viewRenderer ?: return
             viewRenderer.onRenderList(notifList)
-            dispatchOnAfterRenderList(viewRenderer, notifList)
+            dispatchOnAfterRenderList(notifList)
             dispatchOnAfterRenderGroups(viewRenderer, notifList)
             dispatchOnAfterRenderEntries(viewRenderer, notifList)
             viewRenderer.onDispatchComplete()
@@ -85,15 +85,9 @@
             dump("onAfterRenderEntryListeners", onAfterRenderEntryListeners)
         }
 
-    private fun dispatchOnAfterRenderList(
-        viewRenderer: NotifViewRenderer,
-        entries: List<ListEntry>,
-    ) {
+    private fun dispatchOnAfterRenderList(entries: List<ListEntry>) {
         traceSection("RenderStageManager.dispatchOnAfterRenderList") {
-            val stackController = viewRenderer.getStackController()
-            onAfterRenderListListeners.forEach { listener ->
-                listener.onAfterRenderList(entries, stackController)
-            }
+            onAfterRenderListListeners.forEach { listener -> listener.onAfterRenderList(entries) }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
index 3c838e5..72316bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
@@ -41,7 +41,6 @@
 constructor(
     @ShadeDisplayAware context: Context,
     @Assisted listContainer: NotificationListContainer,
-    @Assisted private val stackController: NotifStackController,
     mediaContainerController: MediaContainerController,
     featureManager: NotificationSectionsFeatureManager,
     sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider,
@@ -83,8 +82,6 @@
                 }
             }
 
-            override fun getStackController(): NotifStackController = stackController
-
             override fun getGroupController(group: GroupEntry): NotifGroupController =
                 viewBarn.requireGroupController(group.requireSummary)
 
@@ -95,8 +92,5 @@
 
 @AssistedFactory
 interface ShadeViewManagerFactory {
-    fun create(
-        listContainer: NotificationListContainer,
-        stackController: NotifStackController,
-    ): ShadeViewManager
+    fun create(listContainer: NotificationListContainer): ShadeViewManager
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/model/NotifStats.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/model/NotifStats.kt
new file mode 100644
index 0000000..d7fd702
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/model/NotifStats.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.data.model
+
+/** Information about the current list of notifications. */
+data class NotifStats(
+    val hasNonClearableAlertingNotifs: Boolean,
+    val hasClearableAlertingNotifs: Boolean,
+    val hasNonClearableSilentNotifs: Boolean,
+    val hasClearableSilentNotifs: Boolean,
+) {
+    companion object {
+        @JvmStatic
+        val empty =
+            NotifStats(
+                hasNonClearableAlertingNotifs = false,
+                hasClearableAlertingNotifs = false,
+                hasNonClearableSilentNotifs = false,
+                hasClearableSilentNotifs = false,
+            )
+    }
+}
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 2b9e493..70f06eb 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
@@ -16,7 +16,7 @@
 package com.android.systemui.statusbar.notification.data.repository
 
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.notification.collection.render.NotifStats
+import com.android.systemui.statusbar.notification.data.model.NotifStats
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore.Key
 import com.android.systemui.statusbar.notification.shared.ActiveNotificationEntryModel
 import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
index 6b93ee1..0c040c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
@@ -18,7 +18,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
-import com.android.systemui.statusbar.notification.collection.render.NotifStats
+import com.android.systemui.statusbar.notification.data.model.NotifStats
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
 import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel
 import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
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 2c5d9c2..3c2051f 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
@@ -20,7 +20,6 @@
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
 import com.android.systemui.statusbar.NotificationPresenter
 import com.android.systemui.statusbar.notification.NotificationActivityStarter
-import com.android.systemui.statusbar.notification.collection.render.NotifStackController
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
 
 /**
@@ -33,7 +32,6 @@
     fun initialize(
         presenter: NotificationPresenter,
         listContainer: NotificationListContainer,
-        stackController: NotifStackController,
         notificationActivityStarter: NotificationActivityStarter,
     )
 
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 ea6a60b..0a9899e 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
@@ -34,7 +34,6 @@
 import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
-import com.android.systemui.statusbar.notification.collection.render.NotifStackController
 import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
 import com.android.systemui.statusbar.notification.logging.NotificationLogger
 import com.android.systemui.statusbar.notification.row.NotifBindPipelineInitializer
@@ -76,7 +75,6 @@
     override fun initialize(
         presenter: NotificationPresenter,
         listContainer: NotificationListContainer,
-        stackController: NotifStackController,
         notificationActivityStarter: NotificationActivityStarter,
     ) {
         notificationListener.registerAsSystemService()
@@ -101,7 +99,7 @@
 
         notifPipelineInitializer
             .get()
-            .initialize(notificationListener, notificationRowBinder, listContainer, stackController)
+            .initialize(notificationListener, notificationRowBinder, listContainer)
 
         targetSdkResolver.initialize(notifPipeline.get())
         notificationsMediaManager.setUpWithPresenter(presenter)
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 148b3f0..92d96f9 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
@@ -21,7 +21,6 @@
 import com.android.systemui.statusbar.NotificationListener
 import com.android.systemui.statusbar.NotificationPresenter
 import com.android.systemui.statusbar.notification.NotificationActivityStarter
-import com.android.systemui.statusbar.notification.collection.render.NotifStackController
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
 import javax.inject.Inject
 
@@ -35,7 +34,6 @@
     override fun initialize(
         presenter: NotificationPresenter,
         listContainer: NotificationListContainer,
-        stackController: NotifStackController,
         notificationActivityStarter: NotificationActivityStarter,
     ) {
         // Always connect the listener even if notification-handling is disabled. Being a listener
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 7e3d004..95604c1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -1267,6 +1267,9 @@
         }
         if (mExpandedWhenPinned) {
             return Math.max(getMaxExpandHeight(), getHeadsUpHeight());
+        } else if (android.app.Flags.compactHeadsUpNotification()
+                && getShowingLayout().isHUNCompact()) {
+            return getHeadsUpHeight();
         } else if (atLeastMinHeight) {
             return Math.max(getCollapsedHeight(), getHeadsUpHeight());
         } else {
@@ -3680,6 +3683,10 @@
         return super.disallowSingleClick(event);
     }
 
+    // TODO: b/388470175 - Although this does get triggered when a notification
+    // is expanded by the system (e.g. the first notication in the shade), it
+    // will not be when a notification is collapsed by the system (such as when
+    // the shade is closed).
     private void onExpansionChanged(boolean userAction, boolean wasExpanded) {
         boolean nowExpanded = isExpanded();
         if (mIsSummaryWithChildren && (!mIsMinimized || wasExpanded)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 786d7d9..0d29981 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -207,6 +207,8 @@
     private boolean mContentAnimating;
     private UiEventLogger mUiEventLogger;
 
+    private boolean mIsHUNCompact;
+
     public NotificationContentView(Context context, AttributeSet attrs) {
         super(context, attrs);
         mHybridGroupManager = new HybridGroupManager(getContext());
@@ -543,6 +545,7 @@
         if (child == null) {
             mHeadsUpChild = null;
             mHeadsUpWrapper = null;
+            mIsHUNCompact = false;
             if (mTransformationStartVisibleType == VISIBLE_TYPE_HEADSUP) {
                 mTransformationStartVisibleType = VISIBLE_TYPE_NONE;
             }
@@ -556,8 +559,9 @@
         mHeadsUpWrapper = NotificationViewWrapper.wrap(getContext(), child,
                 mContainingNotification);
 
-        if (Flags.compactHeadsUpNotification()
-                && mHeadsUpWrapper instanceof NotificationCompactHeadsUpTemplateViewWrapper) {
+        mIsHUNCompact = Flags.compactHeadsUpNotification()
+                && mHeadsUpWrapper instanceof NotificationCompactHeadsUpTemplateViewWrapper;
+        if (mIsHUNCompact) {
             logCompactHUNShownEvent();
         }
 
@@ -902,6 +906,10 @@
         }
     }
 
+    public boolean isHUNCompact() {
+        return mIsHUNCompact;
+    }
+
     private boolean isGroupExpanded() {
         return mContainingNotification.isGroupExpanded();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index b892beb..c717e3b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -35,6 +35,8 @@
 import android.animation.ObjectAnimator;
 import android.content.res.Configuration;
 import android.graphics.Point;
+import android.graphics.RenderEffect;
+import android.graphics.Shader;
 import android.os.Trace;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -103,9 +105,7 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider;
 import com.android.systemui.statusbar.notification.collection.provider.VisibilityLocationProviderDelegator;
-import com.android.systemui.statusbar.notification.collection.render.DefaultNotifStackController;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
-import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
 import com.android.systemui.statusbar.notification.headsup.HeadsUpNotificationViewControllerEmptyImpl;
@@ -211,9 +211,6 @@
 
     private final NotificationListContainerImpl mNotificationListContainer =
             new NotificationListContainerImpl();
-    // TODO: b/293167744 - Remove this.
-    private final NotifStackController mNotifStackController =
-            new DefaultNotifStackController();
 
     @VisibleForTesting
     final View.OnAttachStateChangeListener mOnAttachStateChangeListener =
@@ -1242,6 +1239,22 @@
         updateAlpha();
     }
 
+    /**
+     * Applies a blur effect to the view.
+     *
+     * @param blurRadius Radius of blur
+     */
+    public void setBlurRadius(float blurRadius) {
+        if (blurRadius > 0.0f) {
+            mView.setRenderEffect(RenderEffect.createBlurEffect(
+                    blurRadius,
+                    blurRadius,
+                    Shader.TileMode.CLAMP));
+        } else {
+            mView.setRenderEffect(null);
+        }
+    }
+
     private void updateAlpha() {
         if (mView != null) {
             mView.setAlpha(Math.min(Math.min(mMaxAlphaFromView, mMaxAlphaForKeyguard),
@@ -1469,10 +1482,6 @@
         return mNotificationListContainer;
     }
 
-    public NotifStackController getNotifStackController() {
-        return mNotifStackController;
-    }
-
     public void resetCheckSnoozeLeavebehind() {
         mView.resetCheckSnoozeLeavebehind();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index 0b2b84e..3ea4d48 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -179,6 +179,10 @@
                         }
                     }
 
+                    if (Flags.bouncerUiRevamp()) {
+                        launch { viewModel.blurRadius.collect { controller.setBlurRadius(it) } }
+                    }
+
                     if (communalSettingsInteractor.isCommunalFlagEnabled()) {
                         launch {
                             viewModel.glanceableHubAlpha.collect {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index fc8c70f..f0455fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -42,6 +42,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
 import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE
 import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
+import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition
 import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToPrimaryBouncerTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
@@ -154,6 +155,7 @@
     private val primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel,
     private val primaryBouncerToLockscreenTransitionViewModel:
         PrimaryBouncerToLockscreenTransitionViewModel,
+    private val primaryBouncerTransitions: Set<@JvmSuppressWildcards PrimaryBouncerTransition>,
     aodBurnInViewModel: AodBurnInViewModel,
     private val communalSceneInteractor: CommunalSceneInteractor,
     // Lazy because it's only used in the SceneContainer + Dual Shade configuration.
@@ -562,7 +564,7 @@
             lockscreenToDreamingTransitionViewModel.lockscreenAlpha,
             lockscreenToGoneTransitionViewModel.notificationAlpha(viewState),
             lockscreenToOccludedTransitionViewModel.lockscreenAlpha,
-            lockscreenToPrimaryBouncerTransitionViewModel.lockscreenAlpha,
+            lockscreenToPrimaryBouncerTransitionViewModel.notificationAlpha,
             alternateBouncerToPrimaryBouncerTransitionViewModel.notificationAlpha,
             occludedToAodTransitionViewModel.lockscreenAlpha,
             occludedToGoneTransitionViewModel.notificationAlpha(viewState),
@@ -626,6 +628,12 @@
             .dumpWhileCollecting("keyguardAlpha")
     }
 
+    val blurRadius =
+        primaryBouncerTransitions
+            .map { transition -> transition.notificationBlurRadius }
+            .merge()
+            .dumpWhileCollecting("blurRadius")
+
     /**
      * Returns a flow of the expected alpha while running a LOCKSCREEN<->GLANCEABLE_HUB or
      * DREAMING<->GLANCEABLE_HUB transition or idle on the hub.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 3d6cd7e..b146b92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -1492,7 +1492,6 @@
         mNotificationsController.initialize(
                 mPresenterLazy.get(),
                 mNotifListContainer,
-                mStackScrollerController.getNotifStackController(),
                 mNotificationActivityStarterLazy.get());
         mWindowRootViewVisibilityInteractor.setUp(mPresenterLazy.get(), mNotificationsController);
     }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 4abbbac..047b78e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -28,9 +28,11 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
 import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
@@ -159,14 +161,12 @@
         dndModeId = MANUAL_DND_INACTIVE.id
         zenModeRepository.addMode(MANUAL_DND_INACTIVE)
 
-        repository = FakeKeyguardRepository()
+        repository = kosmos.fakeKeyguardRepository
 
-        val withDeps = KeyguardInteractorFactory.create(repository = repository)
-
-        withDeps.featureFlags.apply { set(Flags.REGION_SAMPLING, false) }
+        kosmos.fakeFeatureFlagsClassic.set(Flags.REGION_SAMPLING, false)
         underTest =
             ClockEventController(
-                withDeps.keyguardInteractor,
+                kosmos.keyguardInteractor,
                 keyguardTransitionInteractor,
                 broadcastDispatcher,
                 batteryController,
@@ -177,7 +177,7 @@
                 mainExecutor,
                 bgExecutor,
                 clockBuffers,
-                withDeps.featureFlags,
+                kosmos.fakeFeatureFlagsClassic,
                 zenModeController,
                 kosmos.zenModeInteractor,
                 userTracker,
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 a3c5181..f31d490 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
@@ -26,7 +26,6 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
 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.withArgCaptor
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -48,7 +47,6 @@
 
     private val pipeline: NotifPipeline = mock()
     private val notifLiveDataStoreImpl: NotifLiveDataStoreImpl = mock()
-    private val stackController: NotifStackController = mock()
     private val section: NotifSection = mock()
 
     @Before
@@ -63,7 +61,7 @@
 
     @Test
     fun testUpdateDataStore_withOneEntry() {
-        afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
+        afterRenderListListener.onAfterRenderList(listOf(entry))
         verify(notifLiveDataStoreImpl).setActiveNotifList(eq(listOf(entry)))
         verifyNoMoreInteractions(notifLiveDataStoreImpl)
     }
@@ -86,8 +84,7 @@
                     .setSection(section)
                     .build(),
                 notificationEntry("baz", 1),
-            ),
-            stackController,
+            )
         )
         val list: List<NotificationEntry> = withArgCaptor {
             verify(notifLiveDataStoreImpl).setActiveNotifList(capture())
@@ -111,7 +108,7 @@
 
     @Test
     fun testUpdateDataStore_withZeroEntries_whenNewPipelineEnabled() {
-        afterRenderListListener.onAfterRenderList(listOf(), stackController)
+        afterRenderListListener.onAfterRenderList(listOf())
         verify(notifLiveDataStoreImpl).setActiveNotifList(eq(listOf()))
         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 77bac59..97e99b9 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
@@ -28,8 +28,7 @@
 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.GroupExpansionManagerImpl
-import com.android.systemui.statusbar.notification.collection.render.NotifStackController
-import com.android.systemui.statusbar.notification.collection.render.NotifStats
+import com.android.systemui.statusbar.notification.data.model.NotifStats
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
@@ -43,7 +42,6 @@
 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
@@ -61,7 +59,6 @@
     private val sensitiveNotificationProtectionController:
         SensitiveNotificationProtectionController =
         mock()
-    private val stackController: NotifStackController = mock()
     private val section: NotifSection = mock()
     private val row: ExpandableNotificationRow = mock()
 
@@ -87,25 +84,23 @@
 
     @Test
     fun testSetRenderedListOnInteractor() {
-        afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
+        afterRenderListListener.onAfterRenderList(listOf(entry))
         verify(renderListInteractor).setRenderedList(eq(listOf(entry)))
     }
 
     @Test
     fun testSetNotificationStats_clearableAlerting() {
         whenever(section.bucket).thenReturn(BUCKET_ALERTING)
-        afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
+        afterRenderListListener.onAfterRenderList(listOf(entry))
         verify(activeNotificationsInteractor)
             .setNotifStats(
                 NotifStats(
-                    1,
                     hasNonClearableAlertingNotifs = false,
                     hasClearableAlertingNotifs = true,
                     hasNonClearableSilentNotifs = false,
                     hasClearableSilentNotifs = false,
                 )
             )
-        verifyNoMoreInteractions(stackController)
     }
 
     @Test
@@ -113,35 +108,31 @@
     fun testSetNotificationStats_isSensitiveStateActive_nonClearableAlerting() {
         whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
         whenever(section.bucket).thenReturn(BUCKET_ALERTING)
-        afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
+        afterRenderListListener.onAfterRenderList(listOf(entry))
         verify(activeNotificationsInteractor)
             .setNotifStats(
                 NotifStats(
-                    1,
                     hasNonClearableAlertingNotifs = true,
                     hasClearableAlertingNotifs = false,
                     hasNonClearableSilentNotifs = false,
                     hasClearableSilentNotifs = false,
                 )
             )
-        verifyNoMoreInteractions(stackController)
     }
 
     @Test
     fun testSetNotificationStats_clearableSilent() {
         whenever(section.bucket).thenReturn(BUCKET_SILENT)
-        afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
+        afterRenderListListener.onAfterRenderList(listOf(entry))
         verify(activeNotificationsInteractor)
             .setNotifStats(
                 NotifStats(
-                    1,
                     hasNonClearableAlertingNotifs = false,
                     hasClearableAlertingNotifs = false,
                     hasNonClearableSilentNotifs = false,
                     hasClearableSilentNotifs = true,
                 )
             )
-        verifyNoMoreInteractions(stackController)
     }
 
     @Test
@@ -149,35 +140,31 @@
     fun testSetNotificationStats_isSensitiveStateActive_nonClearableSilent() {
         whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
         whenever(section.bucket).thenReturn(BUCKET_SILENT)
-        afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
+        afterRenderListListener.onAfterRenderList(listOf(entry))
         verify(activeNotificationsInteractor)
             .setNotifStats(
                 NotifStats(
-                    1,
                     hasNonClearableAlertingNotifs = false,
                     hasClearableAlertingNotifs = false,
                     hasNonClearableSilentNotifs = true,
                     hasClearableSilentNotifs = false,
                 )
             )
-        verifyNoMoreInteractions(stackController)
     }
 
     @Test
     fun testSetNotificationStats_nonClearableRedacted() {
         entry.setSensitive(true, true)
         whenever(section.bucket).thenReturn(BUCKET_ALERTING)
-        afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
+        afterRenderListListener.onAfterRenderList(listOf(entry))
         verify(activeNotificationsInteractor)
             .setNotifStats(
                 NotifStats(
-                    1,
                     hasNonClearableAlertingNotifs = true,
                     hasClearableAlertingNotifs = false,
                     hasNonClearableSilentNotifs = false,
                     hasClearableSilentNotifs = false,
                 )
             )
-        verifyNoMoreInteractions(stackController)
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
index 3de8093..ee21bdc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt
@@ -24,8 +24,6 @@
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.shade.data.repository.FakeShadeRepository
 import com.android.systemui.util.mockito.mock
@@ -55,7 +53,6 @@
         fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor = mock(),
         fromOccludedTransitionInteractor: FromOccludedTransitionInteractor = mock(),
         fromAlternateBouncerTransitionInteractor: FromAlternateBouncerTransitionInteractor = mock(),
-        powerInteractor: PowerInteractor = PowerInteractorFactory.create().powerInteractor,
         testScope: CoroutineScope = TestScope(),
     ): WithDependencies {
         // Mock these until they are replaced by kosmos
@@ -73,10 +70,8 @@
             bouncerRepository = bouncerRepository,
             configurationRepository = configurationRepository,
             shadeRepository = shadeRepository,
-            powerInteractor = powerInteractor,
             KeyguardInteractor(
                 repository = repository,
-                powerInteractor = powerInteractor,
                 bouncerRepository = bouncerRepository,
                 configurationInteractor = ConfigurationInteractorImpl(configurationRepository),
                 shadeRepository = shadeRepository,
@@ -99,7 +94,6 @@
         val bouncerRepository: FakeKeyguardBouncerRepository,
         val configurationRepository: FakeConfigurationRepository,
         val shadeRepository: FakeShadeRepository,
-        val powerInteractor: PowerInteractor,
         val keyguardInteractor: KeyguardInteractor,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
index f5f8ef7..869bae2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt
@@ -21,7 +21,6 @@
 import com.android.systemui.keyguard.data.repository.keyguardRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.shade.data.repository.shadeRepository
 
@@ -29,7 +28,6 @@
     Kosmos.Fixture {
         KeyguardInteractor(
             repository = keyguardRepository,
-            powerInteractor = powerInteractor,
             bouncerRepository = keyguardBouncerRepository,
             configurationInteractor = configurationInteractor,
             shadeRepository = shadeRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/transitions/FakeBouncerTransition.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/transitions/FakeBouncerTransition.kt
index 15d00d9..edc1cce 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/transitions/FakeBouncerTransition.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/transitions/FakeBouncerTransition.kt
@@ -20,4 +20,5 @@
 
 class FakeBouncerTransition : PrimaryBouncerTransition {
     override val windowBlurRadius: MutableStateFlow<Float> = MutableStateFlow(0.0f)
+    override val notificationBlurRadius: MutableStateFlow<Float> = MutableStateFlow(0.0f)
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
index d1619b7..60e092c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
@@ -57,6 +57,7 @@
 import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
 import com.android.systemui.unfold.domain.interactor.unfoldTransitionInteractor
+import com.android.systemui.window.ui.viewmodel.fakeBouncerTransitions
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -99,6 +100,7 @@
         primaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel,
         primaryBouncerToLockscreenTransitionViewModel =
             primaryBouncerToLockscreenTransitionViewModel,
+        primaryBouncerTransitions = fakeBouncerTransitions,
         aodBurnInViewModel = aodBurnInViewModel,
         communalSceneInteractor = communalSceneInteractor,
         headsUpNotificationInteractor = { headsUpNotificationInteractor },
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b536dc5..5a198a1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -19374,7 +19374,7 @@
             }
             if (preventIntentRedirectCollectNestedKeysOnServerIfNotCollected()) {
                 // this flag will be ramped to public.
-                intent.collectExtraIntentKeys();
+                intent.collectExtraIntentKeys(true);
             }
         }
 
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index bd27142..3f6484f 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -8571,6 +8571,12 @@
         return true;
     }
 
+    private boolean shouldPreserveVolume(boolean userSwitch, VolumeGroupState vgs) {
+        // as for STREAM_MUSIC, preserve volume from one user to the next except
+        // Android Automotive platform
+        return (userSwitch && vgs.isMusic()) && !isPlatformAutomotive();
+    }
+
     private void readVolumeGroupsSettings(boolean userSwitch) {
         synchronized (mSettingsLock) {
             synchronized (VolumeStreamState.class) {
@@ -8579,8 +8585,7 @@
                 }
                 for (int i = 0; i < sVolumeGroupStates.size(); i++) {
                     VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
-                    // as for STREAM_MUSIC, preserve volume from one user to the next.
-                    if (!(userSwitch && vgs.isMusic())) {
+                    if (!shouldPreserveVolume(userSwitch, vgs)) {
                         vgs.clearIndexCache();
                         vgs.readSettings();
                     }
@@ -9019,6 +9024,11 @@
             mIndexMap.clear();
         }
 
+        private @UserIdInt int getVolumePersistenceUserId() {
+            return isMusic() && !isPlatformAutomotive()
+                    ? UserHandle.USER_SYSTEM : UserHandle.USER_CURRENT;
+        }
+
         private void persistVolumeGroup(int device) {
             // No need to persist the index if the volume group is backed up
             // by a public stream type as this is redundant
@@ -9036,7 +9046,7 @@
             boolean success = mSettings.putSystemIntForUser(mContentResolver,
                     getSettingNameForDevice(device),
                     getIndex(device),
-                    isMusic() ? UserHandle.USER_SYSTEM : UserHandle.USER_CURRENT);
+                    getVolumePersistenceUserId());
             if (!success) {
                 Log.e(TAG, "persistVolumeGroup failed for group " +  mAudioVolumeGroup.name());
             }
@@ -9059,7 +9069,7 @@
                     String name = getSettingNameForDevice(device);
                     index = mSettings.getSystemIntForUser(
                             mContentResolver, name, defaultIndex,
-                            isMusic() ? UserHandle.USER_SYSTEM : UserHandle.USER_CURRENT);
+                            getVolumePersistenceUserId());
                     if (index == -1) {
                         continue;
                     }
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index c8192e5..b530da2 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -2246,10 +2246,6 @@
 
     @GuardedBy("mSyncRoot")
     private void handleLogicalDisplayDisconnectedLocked(LogicalDisplay display) {
-        if (!mFlags.isConnectedDisplayManagementEnabled()) {
-            Slog.e(TAG, "DisplayDisconnected shouldn't be received when the flag is off");
-            return;
-        }
         releaseDisplayAndEmitEvent(display, DisplayManagerGlobal.EVENT_DISPLAY_DISCONNECTED);
         mExternalDisplayPolicy.handleLogicalDisplayDisconnectedLocked(display);
     }
@@ -2315,11 +2311,6 @@
 
     @SuppressLint("AndroidFrameworkRequiresPermission")
     private void handleLogicalDisplayConnectedLocked(LogicalDisplay display) {
-        if (!mFlags.isConnectedDisplayManagementEnabled()) {
-            Slog.e(TAG, "DisplayConnected shouldn't be received when the flag is off");
-            return;
-        }
-
         setupLogicalDisplay(display);
 
         if (ExternalDisplayPolicy.isExternalDisplayLocked(display)) {
@@ -2346,9 +2337,6 @@
     private void handleLogicalDisplayAddedLocked(LogicalDisplay display) {
         final int displayId = display.getDisplayIdLocked();
         final boolean isDefault = displayId == Display.DEFAULT_DISPLAY;
-        if (!mFlags.isConnectedDisplayManagementEnabled()) {
-            setupLogicalDisplay(display);
-        }
 
         // Wake up waitForDefaultDisplay.
         if (isDefault) {
@@ -2443,21 +2431,17 @@
     }
 
     private void handleLogicalDisplayRemovedLocked(@NonNull LogicalDisplay display) {
-        // With display management, the display is removed when disabled, and it might still exist.
+        // The display is removed when disabled, and it might still exist.
         // Resources must only be released when the disconnected signal is received.
-        if (mFlags.isConnectedDisplayManagementEnabled()) {
-            if (display.isValidLocked()) {
-                updateViewportPowerStateLocked(display);
-            }
+        if (display.isValidLocked()) {
+            updateViewportPowerStateLocked(display);
+        }
 
-            // Note: This method is only called if the display was enabled before being removed.
-            sendDisplayEventLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
+        // Note: This method is only called if the display was enabled before being removed.
+        sendDisplayEventLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
 
-            if (display.isValidLocked()) {
-                applyDisplayChangedLocked(display);
-            }
-        } else {
-            releaseDisplayAndEmitEvent(display, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
+        if (display.isValidLocked()) {
+            applyDisplayChangedLocked(display);
         }
         if (mDisplayTopologyCoordinator != null) {
             mDisplayTopologyCoordinator.onDisplayRemoved(display.getDisplayIdLocked());
@@ -4565,13 +4549,11 @@
             final int callingPid = Binder.getCallingPid();
             final int callingUid = Binder.getCallingUid();
 
-            if (mFlags.isConnectedDisplayManagementEnabled()) {
-                if ((internalEventFlagsMask
-                        & DisplayManagerGlobal
-                        .INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED) != 0) {
-                    mContext.enforceCallingOrSelfPermission(MANAGE_DISPLAYS,
-                            "Permission required to get signals about connection events.");
-                }
+            if ((internalEventFlagsMask
+                    & DisplayManagerGlobal
+                    .INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED) != 0) {
+                mContext.enforceCallingOrSelfPermission(MANAGE_DISPLAYS,
+                        "Permission required to get signals about connection events.");
             }
 
             final long token = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index e46397b..f6b2591 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -179,12 +179,10 @@
         pw.println("    Sets brightness to docked + idle screen brightness mode");
         pw.println("  undock");
         pw.println("    Sets brightness to active (normal) screen brightness mode");
-        if (mFlags.isConnectedDisplayManagementEnabled()) {
-            pw.println("  enable-display DISPLAY_ID");
-            pw.println("    Enable the DISPLAY_ID. Only possible if this is a connected display.");
-            pw.println("  disable-display DISPLAY_ID");
-            pw.println("    Disable the DISPLAY_ID. Only possible if this is a connected display.");
-        }
+        pw.println("  enable-display DISPLAY_ID");
+        pw.println("    Enable the DISPLAY_ID. Only possible if this is a connected display.");
+        pw.println("  disable-display DISPLAY_ID");
+        pw.println("    Disable the DISPLAY_ID. Only possible if this is a connected display.");
         pw.println("  power-reset DISPLAY_ID");
         pw.println("    Turn the DISPLAY_ID power to a state the display supposed to have.");
         pw.println("  power-off DISPLAY_ID");
@@ -601,11 +599,6 @@
     }
 
     private int setDisplayEnabled(boolean enable) {
-        if (!mFlags.isConnectedDisplayManagementEnabled()) {
-            getErrPrintWriter()
-                    .println("Error: external display management is not available on this device.");
-            return 1;
-        }
         final String displayIdText = getNextArg();
         if (displayIdText == null) {
             getErrPrintWriter().println("Error: no displayId specified");
diff --git a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
index 519763a..a47853c 100644
--- a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
+++ b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
@@ -142,14 +142,6 @@
             mDisplayIdsWaitingForBootCompletion.clear();
         }
 
-        if (!mFlags.isConnectedDisplayManagementEnabled()) {
-            if (DEBUG) {
-                Slog.d(TAG, "External display management is not enabled on your device:"
-                                    + " cannot register thermal listener.");
-            }
-            return;
-        }
-
         if (!mFlags.isConnectedDisplayErrorHandlingEnabled()) {
             if (DEBUG) {
                 Slog.d(TAG, "ConnectedDisplayErrorHandlingEnabled is not enabled on your device:"
@@ -173,14 +165,6 @@
             return;
         }
 
-        if (!mFlags.isConnectedDisplayManagementEnabled()) {
-            if (DEBUG) {
-                Slog.d(TAG, "setExternalDisplayEnabledLocked: External display management is not"
-                                    + " enabled on your device, cannot enable/disable display.");
-            }
-            return;
-        }
-
         if (enabled && !isExternalDisplayAllowed()) {
             Slog.w(TAG, "setExternalDisplayEnabledLocked: External display can not be enabled"
                                 + " because it is currently not allowed.");
@@ -202,14 +186,6 @@
             return;
         }
 
-        if (!mFlags.isConnectedDisplayManagementEnabled()) {
-            if (DEBUG) {
-                Slog.d(TAG, "handleExternalDisplayConnectedLocked connected display management"
-                                    + " flag is off");
-            }
-            return;
-        }
-
         if (!mIsBootCompleted) {
             mDisplayIdsWaitingForBootCompletion.add(logicalDisplay.getDisplayIdLocked());
             return;
@@ -251,10 +227,6 @@
     void handleLogicalDisplayDisconnectedLocked(@NonNull final LogicalDisplay logicalDisplay) {
         // Type of the display here is always UNKNOWN, so we can't verify it is an external display
 
-        if (!mFlags.isConnectedDisplayManagementEnabled()) {
-            return;
-        }
-
         var displayId = logicalDisplay.getDisplayIdLocked();
         if (mDisplayIdsWaitingForBootCompletion.remove(displayId)) {
             return;
@@ -271,10 +243,6 @@
             return;
         }
 
-        if (!mFlags.isConnectedDisplayManagementEnabled()) {
-            return;
-        }
-
         mExternalDisplayStatsService.onDisplayAdded(logicalDisplay.getDisplayIdLocked());
     }
 
@@ -289,10 +257,6 @@
             }
         }
 
-        if (!mFlags.isConnectedDisplayManagementEnabled()) {
-            return;
-        }
-
         if (isShown) {
             mExternalDisplayStatsService.onPresentationWindowAdded(displayId);
         } else {
@@ -306,12 +270,6 @@
             return;
         }
 
-        if (!mFlags.isConnectedDisplayManagementEnabled()) {
-            Slog.e(TAG, "disableExternalDisplayLocked shouldn't be called when the"
-                                + " connected display management flag is off");
-            return;
-        }
-
         if (!mFlags.isConnectedDisplayErrorHandlingEnabled()) {
             if (DEBUG) {
                 Slog.d(TAG, "disableExternalDisplayLocked shouldn't be called when the"
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 0069215..ecc8896 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -823,18 +823,13 @@
                 if (wasPreviouslyUpdated) {
                     // The display isn't actually removed from our internal data structures until
                     // after the notification is sent; see {@link #sendUpdatesForDisplaysLocked}.
-                    if (mFlags.isConnectedDisplayManagementEnabled()) {
-                        if (mDisplaysEnabledCache.get(displayId)) {
-                            // We still need to send LOGICAL_DISPLAY_EVENT_DISCONNECTED
-                            reloop = true;
-                            logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_REMOVED;
-                        } else {
-                            mUpdatedLogicalDisplays.delete(displayId);
-                            logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_DISCONNECTED;
-                        }
+                    if (mDisplaysEnabledCache.get(displayId)) {
+                        // We still need to send LOGICAL_DISPLAY_EVENT_DISCONNECTED
+                        reloop = true;
+                        logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_REMOVED;
                     } else {
                         mUpdatedLogicalDisplays.delete(displayId);
-                        logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_REMOVED;
+                        logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_DISCONNECTED;
                     }
                 } else {
                     // This display never left this class, safe to remove without notification
@@ -845,20 +840,15 @@
 
             // The display is new.
             } else if (!wasPreviouslyUpdated) {
-                if (mFlags.isConnectedDisplayManagementEnabled()) {
-                    // We still need to send LOGICAL_DISPLAY_EVENT_ADDED
-                    reloop = true;
-                    logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_CONNECTED;
-                } else {
-                    logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_ADDED;
-                }
+                // We still need to send LOGICAL_DISPLAY_EVENT_ADDED
+                reloop = true;
+                logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_CONNECTED;
             // Underlying displays device has changed to a different one.
             } else if (!TextUtils.equals(mTempDisplayInfo.uniqueId, newDisplayInfo.uniqueId)) {
                 logicalDisplayEventMask |= LOGICAL_DISPLAY_EVENT_SWAPPED;
 
             // Something about the display device has changed.
-            } else if (mFlags.isConnectedDisplayManagementEnabled()
-                    && wasPreviouslyEnabled != isCurrentlyEnabled) {
+            } else if (wasPreviouslyEnabled != isCurrentlyEnabled) {
                 int event = isCurrentlyEnabled ? LOGICAL_DISPLAY_EVENT_ADDED :
                         LOGICAL_DISPLAY_EVENT_REMOVED;
                 logicalDisplayEventMask |= event;
@@ -936,17 +926,13 @@
         sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION);
         sendUpdatesForGroupsLocked(DISPLAY_GROUP_EVENT_ADDED);
         sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_REMOVED);
-        if (mFlags.isConnectedDisplayManagementEnabled()) {
-            sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_DISCONNECTED);
-        }
+        sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_DISCONNECTED);
         sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_BASIC_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()) {
-            sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_CONNECTED);
-        }
+        sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_CONNECTED);
         sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_ADDED);
         sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_HDR_SDR_RATIO_CHANGED);
         sendUpdatesForGroupsLocked(DISPLAY_GROUP_EVENT_CHANGED);
@@ -996,23 +982,15 @@
                         + "display=" + id + " with device=" + uniqueId);
             }
 
-            if (mFlags.isConnectedDisplayManagementEnabled()) {
-                if (logicalDisplayEvent == LOGICAL_DISPLAY_EVENT_ADDED) {
-                    mDisplaysEnabledCache.put(id, true);
-                } else if (logicalDisplayEvent == LOGICAL_DISPLAY_EVENT_REMOVED) {
-                    mDisplaysEnabledCache.delete(id);
-                }
+            if (logicalDisplayEvent == LOGICAL_DISPLAY_EVENT_ADDED) {
+                mDisplaysEnabledCache.put(id, true);
+            } else if (logicalDisplayEvent == LOGICAL_DISPLAY_EVENT_REMOVED) {
+                mDisplaysEnabledCache.delete(id);
             }
 
             mListener.onLogicalDisplayEventLocked(display, logicalDisplayEvent);
 
-            if (mFlags.isConnectedDisplayManagementEnabled()) {
-                if (logicalDisplayEvent == LOGICAL_DISPLAY_EVENT_DISCONNECTED) {
-                    mLogicalDisplays.delete(id);
-                }
-            } else if (logicalDisplayEvent == LOGICAL_DISPLAY_EVENT_REMOVED) {
-                // We wait until we sent the EVENT_REMOVED event before actually removing the
-                // display.
+            if (logicalDisplayEvent == LOGICAL_DISPLAY_EVENT_DISCONNECTED) {
                 mLogicalDisplays.delete(id);
             }
         }
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 addfbf1..4e57d67 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -42,10 +42,6 @@
             Flags.FLAG_ENABLE_PORT_IN_DISPLAY_LAYOUT,
             Flags::enablePortInDisplayLayout);
 
-    private final FlagState mConnectedDisplayManagementFlagState = new FlagState(
-            Flags.FLAG_ENABLE_CONNECTED_DISPLAY_MANAGEMENT,
-            Flags::enableConnectedDisplayManagement);
-
     private final FlagState mAdaptiveToneImprovements1 = new FlagState(
             Flags.FLAG_ENABLE_ADAPTIVE_TONE_IMPROVEMENTS_1,
             Flags::enableAdaptiveToneImprovements1);
@@ -269,11 +265,6 @@
         return mPortInDisplayLayoutFlagState.isEnabled();
     }
 
-    /** Returns whether connected display management is enabled or not. */
-    public boolean isConnectedDisplayManagementEnabled() {
-        return mConnectedDisplayManagementFlagState.isEnabled();
-    }
-
     /** Returns whether power throttling clamper is enabled on not. */
     public boolean isPowerThrottlingClamperEnabled() {
         return mPowerThrottlingClamperFlagState.isEnabled();
@@ -572,7 +563,6 @@
         pw.println(" " + mAdaptiveToneImprovements2);
         pw.println(" " + mBackUpSmoothDisplayAndForcePeakRefreshRateFlagState);
         pw.println(" " + mConnectedDisplayErrorHandlingFlagState);
-        pw.println(" " + mConnectedDisplayManagementFlagState);
         pw.println(" " + mDisplayOffloadFlagState);
         pw.println(" " + mExternalDisplayLimitModeState);
         pw.println(" " + mDisplayTopology);
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 eccbbb1..afae07c 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
@@ -29,14 +29,6 @@
 }
 
 flag {
-    name: "enable_connected_display_management"
-    namespace: "display_manager"
-    description: "Feature flag for Connected Display management"
-    bug: "280739508"
-    is_fixed_read_only: true
-}
-
-flag {
     name: "enable_power_throttling_clamper"
     namespace: "display_manager"
     description: "Feature flag for Power Throttling Clamper"
diff --git a/services/core/java/com/android/server/flags/services.aconfig b/services/core/java/com/android/server/flags/services.aconfig
index eea5c98..4505d0e 100644
--- a/services/core/java/com/android/server/flags/services.aconfig
+++ b/services/core/java/com/android/server/flags/services.aconfig
@@ -78,3 +78,11 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "datetime_notifications"
+    # "location" is used by the Android System Time team for feature flags.
+    namespace: "location"
+    description: "Enable the time notifications feature, a toggle to enable/disable time-related notifications in Date & Time Settings"
+    bug: "283267917"
+}
diff --git a/services/core/java/com/android/server/media/TEST_MAPPING b/services/core/java/com/android/server/media/TEST_MAPPING
index 43e2afd..dbf9915 100644
--- a/services/core/java/com/android/server/media/TEST_MAPPING
+++ b/services/core/java/com/android/server/media/TEST_MAPPING
@@ -1,7 +1,10 @@
 {
   "presubmit": [
     {
-      "name": "CtsMediaBetterTogetherTestCases"
+      "name": "CtsMediaRouterTestCases"
+    },
+    {
+      "name": "CtsMediaSessionTestCases"
     },
     {
       "name": "MediaRouterServiceTests"
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 9567c81..dd9741c 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -3162,6 +3162,7 @@
             mAssistants.onBootPhaseAppsCanStart();
             mConditionProviders.onBootPhaseAppsCanStart();
             mHistoryManager.onBootPhaseAppsCanStart();
+            mPreferencesHelper.onBootPhaseAppsCanStart();
             migrateDefaultNAS();
             maybeShowInitialReviewPermissionsNotification();
 
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 93f512b..0bb3c6a 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -36,10 +36,7 @@
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.Person;
-import android.content.ContentProvider;
-import android.content.ContentResolver;
 import android.content.Context;
-import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ShortcutInfo;
@@ -48,7 +45,6 @@
 import android.media.AudioSystem;
 import android.metrics.LogMaker;
 import android.net.Uri;
-import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -1493,23 +1489,14 @@
 
             final Notification notification = getNotification();
             notification.visitUris((uri) -> {
-                if (com.android.server.notification.Flags.notificationVerifyChannelSoundUri()) {
-                    visitGrantableUri(uri, false, false);
-                } else {
-                    oldVisitGrantableUri(uri, false, false);
-                }
+                visitGrantableUri(uri, false, false);
             });
 
             if (notification.getChannelId() != null) {
                 NotificationChannel channel = getChannel();
                 if (channel != null) {
-                    if (com.android.server.notification.Flags.notificationVerifyChannelSoundUri()) {
-                        visitGrantableUri(channel.getSound(), (channel.getUserLockedFields()
-                                & NotificationChannel.USER_LOCKED_SOUND) != 0, true);
-                    } else {
-                        oldVisitGrantableUri(channel.getSound(), (channel.getUserLockedFields()
-                                & NotificationChannel.USER_LOCKED_SOUND) != 0, true);
-                    }
+                    visitGrantableUri(channel.getSound(), (channel.getUserLockedFields()
+                            & NotificationChannel.USER_LOCKED_SOUND) != 0, true);
                 }
             }
         } finally {
@@ -1525,53 +1512,6 @@
      * {@link #mGrantableUris}. Otherwise, this will either log or throw
      * {@link SecurityException} depending on target SDK of enqueuing app.
      */
-    private void oldVisitGrantableUri(Uri uri, boolean userOverriddenUri, boolean isSound) {
-        if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;
-
-        if (mGrantableUris != null && mGrantableUris.contains(uri)) {
-            return; // already verified this URI
-        }
-
-        final int sourceUid = getSbn().getUid();
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            // This will throw a SecurityException if the caller can't grant.
-            mUgmInternal.checkGrantUriPermission(sourceUid, null,
-                    ContentProvider.getUriWithoutUserId(uri),
-                    Intent.FLAG_GRANT_READ_URI_PERMISSION,
-                    ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(sourceUid)));
-
-            if (mGrantableUris == null) {
-                mGrantableUris = new ArraySet<>();
-            }
-            mGrantableUris.add(uri);
-        } catch (SecurityException e) {
-            if (!userOverriddenUri) {
-                if (isSound) {
-                    mSound = Settings.System.DEFAULT_NOTIFICATION_URI;
-                    Log.w(TAG, "Replacing " + uri + " from " + sourceUid + ": " + e.getMessage());
-                } else {
-                    if (mTargetSdkVersion >= Build.VERSION_CODES.P) {
-                        throw e;
-                    } else {
-                        Log.w(TAG,
-                                "Ignoring " + uri + " from " + sourceUid + ": " + e.getMessage());
-                    }
-                }
-            }
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
-    /**
-     * Note the presence of a {@link Uri} that should have permission granted to
-     * whoever will be rendering it.
-     * <p>
-     * If the enqueuing app has the ability to grant access, it will be added to
-     * {@link #mGrantableUris}. Otherwise, this will either log or throw
-     * {@link SecurityException} depending on target SDK of enqueuing app.
-     */
     private void visitGrantableUri(Uri uri, boolean userOverriddenUri,
             boolean isSound) {
         if (mGrantableUris != null && mGrantableUris.contains(uri)) {
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 15377d6..36eabae 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -82,7 +82,6 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.IntArray;
-import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
@@ -272,6 +271,15 @@
         updateMediaNotificationFilteringEnabled();
     }
 
+    void onBootPhaseAppsCanStart() {
+        // IpcDataCaches must be invalidated once data becomes available, as queries will only
+        // begin to be cached after the first invalidation signal. At this point, we know about all
+        // notification channels.
+        if (android.app.Flags.nmBinderPerfCacheChannels()) {
+            invalidateNotificationChannelCache();
+        }
+    }
+
     public void readXml(TypedXmlPullParser parser, boolean forRestore, int userId)
             throws XmlPullParserException, IOException {
         int type = parser.getEventType();
@@ -531,12 +539,14 @@
     private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg,
             @UserIdInt int userId, int uid, int importance, int priority, int visibility,
             boolean showBadge, int bubblePreference, long creationTime) {
+        boolean created = false;
         final String key = packagePreferencesKey(pkg, uid);
         PackagePreferences
                 r = (uid == UNKNOWN_UID)
                 ? mRestoredWithoutUids.get(unrestoredPackageKey(pkg, userId))
                 : mPackagePreferences.get(key);
         if (r == null) {
+            created = true;
             r = new PackagePreferences();
             r.pkg = pkg;
             r.uid = uid;
@@ -572,6 +582,9 @@
                 mRestoredWithoutUids.remove(unrestoredPackageKey(pkg, userId));
             }
         }
+        if (android.app.Flags.nmBinderPerfCacheChannels() && created) {
+            invalidateNotificationChannelCache();
+        }
         return r;
     }
 
@@ -664,6 +677,9 @@
         }
         NotificationChannel channel = new NotificationChannel(channelId, label, IMPORTANCE_LOW);
         p.channels.put(channelId, channel);
+        if (android.app.Flags.nmBinderPerfCacheChannels()) {
+            invalidateNotificationChannelCache();
+        }
         return channel;
     }
 
@@ -1171,9 +1187,7 @@
                 // Verify that the app has permission to read the sound Uri
                 // Only check for new channels, as regular apps can only set sound
                 // before creating. See: {@link NotificationChannel#setSound}
-                if (Flags.notificationVerifyChannelSoundUri()) {
-                    PermissionHelper.grantUriPermission(mUgmInternal, channel.getSound(), uid);
-                }
+                PermissionHelper.grantUriPermission(mUgmInternal, channel.getSound(), uid);
 
                 channel.setImportanceLockedByCriticalDeviceFunction(
                         r.defaultAppLockedImportance || r.fixedImportance);
@@ -1208,6 +1222,10 @@
             updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
         }
 
+        if (android.app.Flags.nmBinderPerfCacheChannels() && needsPolicyFileChange) {
+            invalidateNotificationChannelCache();
+        }
+
         return needsPolicyFileChange;
     }
 
@@ -1229,6 +1247,9 @@
             }
             channel.unlockFields(USER_LOCKED_IMPORTANCE);
         }
+        if (android.app.Flags.nmBinderPerfCacheChannels()) {
+            invalidateNotificationChannelCache();
+        }
     }
 
 
@@ -1301,6 +1322,9 @@
             updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
         }
         if (changed) {
+            if (android.app.Flags.nmBinderPerfCacheChannels()) {
+                invalidateNotificationChannelCache();
+            }
             updateConfig();
         }
     }
@@ -1537,6 +1561,10 @@
         if (channelBypassedDnd) {
             updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
         }
+
+        if (android.app.Flags.nmBinderPerfCacheChannels() && deletedChannel) {
+            invalidateNotificationChannelCache();
+        }
         return deletedChannel;
     }
 
@@ -1566,6 +1594,9 @@
             }
             r.channels.remove(channelId);
         }
+        if (android.app.Flags.nmBinderPerfCacheChannels()) {
+            invalidateNotificationChannelCache();
+        }
     }
 
     @Override
@@ -1576,13 +1607,18 @@
             if (r == null) {
                 return;
             }
+            boolean deleted = false;
             int N = r.channels.size() - 1;
             for (int i = N; i >= 0; i--) {
                 String key = r.channels.keyAt(i);
                 if (!DEFAULT_CHANNEL_ID.equals(key)) {
                     r.channels.remove(key);
+                    deleted = true;
                 }
             }
+            if (android.app.Flags.nmBinderPerfCacheChannels() && deleted) {
+                invalidateNotificationChannelCache();
+            }
         }
     }
 
@@ -1613,6 +1649,9 @@
                 }
             }
         }
+        if (android.app.Flags.nmBinderPerfCacheChannels()) {
+            invalidateNotificationChannelCache();
+        }
     }
 
     public void updateDefaultApps(int userId, ArraySet<String> toRemove,
@@ -1642,6 +1681,9 @@
                 }
             }
         }
+        if (android.app.Flags.nmBinderPerfCacheChannels()) {
+            invalidateNotificationChannelCache();
+        }
     }
 
     public NotificationChannelGroup getNotificationChannelGroupWithChannels(String pkg,
@@ -1757,6 +1799,9 @@
         if (groupBypassedDnd) {
             updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
         }
+        if (android.app.Flags.nmBinderPerfCacheChannels() && deletedChannels.size() > 0) {
+            invalidateNotificationChannelCache();
+        }
         return deletedChannels;
     }
 
@@ -1902,8 +1947,13 @@
                 }
             }
         }
-        if (!deletedChannelIds.isEmpty() && mCurrentUserHasChannelsBypassingDnd) {
-            updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
+        if (!deletedChannelIds.isEmpty()) {
+            if (mCurrentUserHasChannelsBypassingDnd) {
+                updateCurrentUserHasChannelsBypassingDnd(callingUid, fromSystemOrSystemUi);
+            }
+            if (android.app.Flags.nmBinderPerfCacheChannels()) {
+                invalidateNotificationChannelCache();
+            }
         }
         return deletedChannelIds;
     }
@@ -2196,6 +2246,11 @@
             PackagePreferences prefs = getOrCreatePackagePreferencesLocked(sourcePkg, sourceUid);
             prefs.delegate = new Delegate(delegatePkg, delegateUid, true);
         }
+        if (android.app.Flags.nmBinderPerfCacheChannels()) {
+            // If package delegates change, then which packages can get what channel information
+            // also changes, so we need to clear the cache.
+            invalidateNotificationChannelCache();
+        }
     }
 
     /**
@@ -2208,6 +2263,9 @@
                 prefs.delegate.mEnabled = false;
             }
         }
+        if (android.app.Flags.nmBinderPerfCacheChannels()) {
+            invalidateNotificationChannelCache();
+        }
     }
 
     /**
@@ -2811,18 +2869,24 @@
 
     public void onUserRemoved(int userId) {
         synchronized (mLock) {
+            boolean removed = false;
             int N = mPackagePreferences.size();
             for (int i = N - 1; i >= 0; i--) {
                 PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i);
                 if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
                     mPackagePreferences.removeAt(i);
+                    removed = true;
                 }
             }
+            if (android.app.Flags.nmBinderPerfCacheChannels() && removed) {
+                invalidateNotificationChannelCache();
+            }
         }
     }
 
     protected void onLocaleChanged(Context context, int userId) {
         synchronized (mLock) {
+            boolean updated = false;
             int N = mPackagePreferences.size();
             for (int i = 0; i < N; i++) {
                 PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i);
@@ -2833,10 +2897,14 @@
                                 DEFAULT_CHANNEL_ID).setName(
                                 context.getResources().getString(
                                         R.string.default_notification_channel_label));
+                        updated = true;
                     }
                     // TODO (b/346396459): Localize all reserved channels
                 }
             }
+            if (android.app.Flags.nmBinderPerfCacheChannels() && updated) {
+                invalidateNotificationChannelCache();
+            }
         }
     }
 
@@ -2884,7 +2952,7 @@
                                                     channel.getAudioAttributes().getUsage());
                                     if (Settings.System.DEFAULT_NOTIFICATION_URI.equals(
                                             restoredUri)) {
-                                        Log.w(TAG,
+                                        Slog.w(TAG,
                                                 "Could not restore sound: " + uri + " for channel: "
                                                         + channel);
                                     }
@@ -2922,6 +2990,9 @@
 
         if (updated) {
             updateConfig();
+            if (android.app.Flags.nmBinderPerfCacheChannels()) {
+                invalidateNotificationChannelCache();
+            }
         }
         return updated;
     }
@@ -2939,6 +3010,9 @@
                 p.priority = DEFAULT_PRIORITY;
                 p.visibility = DEFAULT_VISIBILITY;
                 p.showBadge = DEFAULT_SHOW_BADGE;
+                if (android.app.Flags.nmBinderPerfCacheChannels()) {
+                    invalidateNotificationChannelCache();
+                }
             }
         }
     }
@@ -3123,6 +3197,9 @@
                 }
             }
         }
+        if (android.app.Flags.nmBinderPerfCacheChannels()) {
+            invalidateNotificationChannelCache();
+        }
     }
 
     public void migrateNotificationPermissions(List<UserInfo> users) {
@@ -3154,6 +3231,12 @@
         mRankingHandler.requestSort();
     }
 
+    @VisibleForTesting
+    // Utility method for overriding in tests to confirm that the cache gets cleared.
+    protected void invalidateNotificationChannelCache() {
+        NotificationManager.invalidateNotificationChannelCache();
+    }
+
     private static String packagePreferencesKey(String pkg, int uid) {
         return pkg + "|" + uid;
     }
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index 2b4d71e..c1ca9c2 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -172,16 +172,6 @@
 }
 
 flag {
-  name: "notification_verify_channel_sound_uri"
-  namespace: "systemui"
-  description: "Verify Uri permission for sound when creating a notification channel"
-  bug: "337775777"
-  metadata {
-    purpose: PURPOSE_BUGFIX
-  }
-}
-
-flag {
   name: "notification_vibration_in_sound_uri_for_channel"
   namespace: "systemui"
   description: "Enables sound uri with vibration source in notification channel"
diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
index 17d7a14..e1b7622 100644
--- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
+++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
@@ -612,7 +612,7 @@
                 final PackageSetting staticLibPkgSetting =
                         mPm.getPackageSettingForMutation(sharedLibraryInfo.getPackageName());
                 if (staticLibPkgSetting == null) {
-                    Slog.wtf(TAG, "Shared lib without setting: " + sharedLibraryInfo);
+                    Slog.w(TAG, "Shared lib without setting: " + sharedLibraryInfo);
                     continue;
                 }
                 for (int u = 0; u < installedUserCount; u++) {
diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java
index 14539d5..50db1e4 100644
--- a/services/core/java/com/android/server/rollback/RollbackStore.java
+++ b/services/core/java/com/android/server/rollback/RollbackStore.java
@@ -84,8 +84,12 @@
      */
     private static List<Rollback> loadRollbacks(File rollbackDataDir) {
         List<Rollback> rollbacks = new ArrayList<>();
-        rollbackDataDir.mkdirs();
-        for (File rollbackDir : rollbackDataDir.listFiles()) {
+        File[] rollbackDirs = rollbackDataDir.listFiles();
+        if (rollbackDirs == null) {
+            Slog.e(TAG, "Folder doesn't exist: " + rollbackDataDir);
+            return rollbacks;
+        }
+        for (File rollbackDir : rollbackDirs) {
             if (rollbackDir.isDirectory()) {
                 try {
                     rollbacks.add(loadRollback(rollbackDir));
diff --git a/services/core/java/com/android/server/timedetector/ServerFlags.java b/services/core/java/com/android/server/timedetector/ServerFlags.java
index 2049a02..b651c7b 100644
--- a/services/core/java/com/android/server/timedetector/ServerFlags.java
+++ b/services/core/java/com/android/server/timedetector/ServerFlags.java
@@ -72,8 +72,12 @@
             KEY_TIME_ZONE_DETECTOR_AUTO_DETECTION_ENABLED_DEFAULT,
             KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED,
             KEY_ENHANCED_METRICS_COLLECTION_ENABLED,
+            KEY_TIME_ZONE_NOTIFICATIONS_SUPPORTED,
+            KEY_TIME_ZONE_NOTIFICATIONS_ENABLED_DEFAULT,
+            KEY_TIME_ZONE_NOTIFICATIONS_TRACKING_SUPPORTED,
+            KEY_TIME_ZONE_MANUAL_CHANGE_TRACKING_SUPPORTED
     })
-    @Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER })
+    @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
     @Retention(RetentionPolicy.SOURCE)
     @interface DeviceConfigKey {}
 
@@ -192,6 +196,31 @@
             "enhanced_metrics_collection_enabled";
 
     /**
+     * The key to control support for time zone notifications under certain circumstances.
+     */
+    public static final @DeviceConfigKey String KEY_TIME_ZONE_NOTIFICATIONS_SUPPORTED =
+            "time_zone_notifications_supported";
+
+    /**
+     * The key for the default value used to determine whether time zone notifications is enabled
+     * when the user hasn't explicitly set it yet.
+     */
+    public static final @DeviceConfigKey String KEY_TIME_ZONE_NOTIFICATIONS_ENABLED_DEFAULT =
+            "time_zone_notifications_enabled_default";
+
+    /**
+     * The key to control support for time zone notifications tracking under certain circumstances.
+     */
+    public static final @DeviceConfigKey String KEY_TIME_ZONE_NOTIFICATIONS_TRACKING_SUPPORTED =
+            "time_zone_notifications_tracking_supported";
+
+    /**
+     * The key to control support for time zone manual change tracking under certain circumstances.
+     */
+    public static final @DeviceConfigKey String KEY_TIME_ZONE_MANUAL_CHANGE_TRACKING_SUPPORTED =
+            "time_zone_manual_change_tracking_supported";
+
+    /**
      * The registered listeners and the keys to trigger on. The value is explicitly a HashSet to
      * ensure O(1) lookup performance when working out whether a listener should trigger.
      */
diff --git a/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
index 3579246..0495f54 100644
--- a/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
+++ b/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
@@ -286,7 +286,8 @@
         // This check is racey, but the whole settings update process is racey. This check prevents
         // a ConfigurationChangeListener callback triggering due to ContentObserver's still
         // triggering *sometimes* for no-op updates. Because callbacks are async this is necessary
-        // for stable behavior during tests.
+        // for stable behavior during tests. This behavior is copied from
+        // setAutoDetectionEnabledIfRequired and assumed to be the correct way.
         if (getAutoDetectionEnabledSetting() != enabled) {
             Settings.Global.putInt(mCr, Settings.Global.AUTO_TIME, enabled ? 1 : 0);
         }
diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
index fc659c5..c4c86a42 100644
--- a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
+++ b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
@@ -65,6 +65,10 @@
     private final boolean mUserConfigAllowed;
     private final boolean mLocationEnabledSetting;
     private final boolean mGeoDetectionEnabledSetting;
+    private final boolean mNotificationsSupported;
+    private final boolean mNotificationsEnabledSetting;
+    private final boolean mNotificationTrackingSupported;
+    private final boolean mManualChangeTrackingSupported;
 
     private ConfigurationInternal(Builder builder) {
         mTelephonyDetectionSupported = builder.mTelephonyDetectionSupported;
@@ -78,6 +82,10 @@
         mUserConfigAllowed = builder.mUserConfigAllowed;
         mLocationEnabledSetting = builder.mLocationEnabledSetting;
         mGeoDetectionEnabledSetting = builder.mGeoDetectionEnabledSetting;
+        mNotificationsSupported = builder.mNotificationsSupported;
+        mNotificationsEnabledSetting = builder.mNotificationsEnabledSetting;
+        mNotificationTrackingSupported = builder.mNotificationsTrackingSupported;
+        mManualChangeTrackingSupported = builder.mManualChangeTrackingSupported;
     }
 
     /** Returns true if the device supports any form of auto time zone detection. */
@@ -104,6 +112,27 @@
     }
 
     /**
+     * Returns true if the device supports time-related notifications.
+     */
+    public boolean areNotificationsSupported() {
+        return mNotificationsSupported;
+    }
+
+    /**
+     * Returns true if the device supports tracking of time-related notifications.
+     */
+    public boolean isNotificationTrackingSupported() {
+        return areNotificationsSupported() && mNotificationTrackingSupported;
+    }
+
+    /**
+     * Returns true if the device supports tracking of time zone manual changes.
+     */
+    public boolean isManualChangeTrackingSupported() {
+        return mManualChangeTrackingSupported;
+    }
+
+    /**
      * Returns {@code true} if location time zone detection should run when auto time zone detection
      * is enabled on supported devices, even when the user has not enabled the algorithm explicitly
      * in settings. Enabled for internal testing only. See {@link #isGeoDetectionExecutionEnabled()}
@@ -223,6 +252,15 @@
                 && getGeoDetectionRunInBackgroundEnabledSetting();
     }
 
+    /** Returns true if time-related notifications can be shown on this device. */
+    public boolean getNotificationsEnabledBehavior() {
+        return areNotificationsSupported() && getNotificationsEnabledSetting();
+    }
+
+    private boolean getNotificationsEnabledSetting() {
+        return mNotificationsEnabledSetting;
+    }
+
     @NonNull
     public TimeZoneCapabilities asCapabilities(boolean bypassUserPolicyChecks) {
         UserHandle userHandle = UserHandle.of(mUserId);
@@ -283,6 +321,14 @@
         }
         builder.setSetManualTimeZoneCapability(suggestManualTimeZoneCapability);
 
+        final @CapabilityState int configureNotificationsEnabledCapability;
+        if (areNotificationsSupported()) {
+            configureNotificationsEnabledCapability = CAPABILITY_POSSESSED;
+        } else {
+            configureNotificationsEnabledCapability = CAPABILITY_NOT_SUPPORTED;
+        }
+        builder.setConfigureNotificationsEnabledCapability(configureNotificationsEnabledCapability);
+
         return builder.build();
     }
 
@@ -291,6 +337,7 @@
         return new TimeZoneConfiguration.Builder()
                 .setAutoDetectionEnabled(getAutoDetectionEnabledSetting())
                 .setGeoDetectionEnabled(getGeoDetectionEnabledSetting())
+                .setNotificationsEnabled(getNotificationsEnabledSetting())
                 .build();
     }
 
@@ -307,6 +354,9 @@
         if (newConfiguration.hasIsGeoDetectionEnabled()) {
             builder.setGeoDetectionEnabledSetting(newConfiguration.isGeoDetectionEnabled());
         }
+        if (newConfiguration.hasIsNotificationsEnabled()) {
+            builder.setNotificationsEnabledSetting(newConfiguration.areNotificationsEnabled());
+        }
         return builder.build();
     }
 
@@ -328,7 +378,11 @@
                 && mEnhancedMetricsCollectionEnabled == that.mEnhancedMetricsCollectionEnabled
                 && mAutoDetectionEnabledSetting == that.mAutoDetectionEnabledSetting
                 && mLocationEnabledSetting == that.mLocationEnabledSetting
-                && mGeoDetectionEnabledSetting == that.mGeoDetectionEnabledSetting;
+                && mGeoDetectionEnabledSetting == that.mGeoDetectionEnabledSetting
+                && mNotificationsSupported == that.mNotificationsSupported
+                && mNotificationsEnabledSetting == that.mNotificationsEnabledSetting
+                && mNotificationTrackingSupported == that.mNotificationTrackingSupported
+                && mManualChangeTrackingSupported == that.mManualChangeTrackingSupported;
     }
 
     @Override
@@ -336,7 +390,9 @@
         return Objects.hash(mUserId, mUserConfigAllowed, mTelephonyDetectionSupported,
                 mGeoDetectionSupported, mTelephonyFallbackSupported,
                 mGeoDetectionRunInBackgroundEnabled, mEnhancedMetricsCollectionEnabled,
-                mAutoDetectionEnabledSetting, mLocationEnabledSetting, mGeoDetectionEnabledSetting);
+                mAutoDetectionEnabledSetting, mLocationEnabledSetting, mGeoDetectionEnabledSetting,
+                mNotificationsSupported, mNotificationsEnabledSetting,
+                mNotificationTrackingSupported, mManualChangeTrackingSupported);
     }
 
     @Override
@@ -352,6 +408,10 @@
                 + ", mAutoDetectionEnabledSetting=" + mAutoDetectionEnabledSetting
                 + ", mLocationEnabledSetting=" + mLocationEnabledSetting
                 + ", mGeoDetectionEnabledSetting=" + mGeoDetectionEnabledSetting
+                + ", mNotificationsSupported=" + mNotificationsSupported
+                + ", mNotificationsEnabledSetting=" + mNotificationsEnabledSetting
+                + ", mNotificationTrackingSupported=" + mNotificationTrackingSupported
+                + ", mManualChangeTrackingSupported=" + mManualChangeTrackingSupported
                 + '}';
     }
 
@@ -370,6 +430,10 @@
         private boolean mAutoDetectionEnabledSetting;
         private boolean mLocationEnabledSetting;
         private boolean mGeoDetectionEnabledSetting;
+        private boolean mNotificationsSupported;
+        private boolean mNotificationsEnabledSetting;
+        private boolean mNotificationsTrackingSupported;
+        private boolean mManualChangeTrackingSupported;
 
         /**
          * Creates a new Builder.
@@ -390,6 +454,10 @@
             this.mAutoDetectionEnabledSetting = toCopy.mAutoDetectionEnabledSetting;
             this.mLocationEnabledSetting = toCopy.mLocationEnabledSetting;
             this.mGeoDetectionEnabledSetting = toCopy.mGeoDetectionEnabledSetting;
+            this.mNotificationsSupported = toCopy.mNotificationsSupported;
+            this.mNotificationsEnabledSetting = toCopy.mNotificationsEnabledSetting;
+            this.mNotificationsTrackingSupported = toCopy.mNotificationTrackingSupported;
+            this.mManualChangeTrackingSupported = toCopy.mManualChangeTrackingSupported;
         }
 
         /**
@@ -475,6 +543,38 @@
             return this;
         }
 
+        /**
+         * Sets the value of the time notification setting for this user.
+         */
+        public Builder setNotificationsEnabledSetting(boolean enabled) {
+            mNotificationsEnabledSetting = enabled;
+            return this;
+        }
+
+        /**
+         * Sets whether time zone notifications are supported on this device.
+         */
+        public Builder setNotificationsSupported(boolean enabled) {
+            mNotificationsSupported = enabled;
+            return this;
+        }
+
+        /**
+         * Sets whether time zone notification tracking is supported on this device.
+         */
+        public Builder setNotificationsTrackingSupported(boolean supported) {
+            mNotificationsTrackingSupported = supported;
+            return this;
+        }
+
+        /**
+         * Sets whether time zone manual change tracking are supported on this device.
+         */
+        public Builder setManualChangeTrackingSupported(boolean supported) {
+            mManualChangeTrackingSupported = supported;
+            return this;
+        }
+
         /** Returns a new {@link ConfigurationInternal}. */
         @NonNull
         public ConfigurationInternal build() {
diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
index f1248a3..d809fc6 100644
--- a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
@@ -68,7 +68,11 @@
             ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT,
             ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE,
             ServerFlags.KEY_TIME_ZONE_DETECTOR_AUTO_DETECTION_ENABLED_DEFAULT,
-            ServerFlags.KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED
+            ServerFlags.KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED,
+            ServerFlags.KEY_TIME_ZONE_NOTIFICATIONS_SUPPORTED,
+            ServerFlags.KEY_TIME_ZONE_NOTIFICATIONS_ENABLED_DEFAULT,
+            ServerFlags.KEY_TIME_ZONE_NOTIFICATIONS_TRACKING_SUPPORTED,
+            ServerFlags.KEY_TIME_ZONE_MANUAL_CHANGE_TRACKING_SUPPORTED
     );
 
     /**
@@ -100,11 +104,16 @@
     @Nullable
     private static ServiceConfigAccessor sInstance;
 
-    @NonNull private final Context mContext;
-    @NonNull private final ServerFlags mServerFlags;
-    @NonNull private final ContentResolver mCr;
-    @NonNull private final UserManager mUserManager;
-    @NonNull private final LocationManager mLocationManager;
+    @NonNull
+    private final Context mContext;
+    @NonNull
+    private final ServerFlags mServerFlags;
+    @NonNull
+    private final ContentResolver mCr;
+    @NonNull
+    private final UserManager mUserManager;
+    @NonNull
+    private final LocationManager mLocationManager;
 
     @GuardedBy("this")
     @NonNull
@@ -193,6 +202,9 @@
         contentResolver.registerContentObserver(
                 Settings.Global.getUriFor(Settings.Global.AUTO_TIME_ZONE_EXPLICIT), true,
                 contentObserver);
+        contentResolver.registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.TIME_ZONE_NOTIFICATIONS), true,
+                contentObserver);
 
         // Add async callbacks for user scoped location settings being changed.
         contentResolver.registerContentObserver(
@@ -331,6 +343,14 @@
                 setGeoDetectionEnabledSettingIfRequired(userId, geoDetectionEnabledSetting);
             }
         }
+
+        if (areNotificationsSupported()) {
+            if (requestedConfigurationUpdates.hasIsNotificationsEnabled()) {
+                setNotificationsEnabledSetting(
+                        requestedConfigurationUpdates.areNotificationsEnabled());
+            }
+            setNotificationsEnabledIfRequired(newConfiguration.areNotificationsEnabled());
+        }
     }
 
     @Override
@@ -348,6 +368,10 @@
                 .setUserConfigAllowed(isUserConfigAllowed(userId))
                 .setLocationEnabledSetting(getLocationEnabledSetting(userId))
                 .setGeoDetectionEnabledSetting(getGeoDetectionEnabledSetting(userId))
+                .setNotificationsSupported(areNotificationsSupported())
+                .setNotificationsEnabledSetting(getNotificationsEnabledSetting())
+                .setNotificationsTrackingSupported(isNotificationTrackingSupported())
+                .setManualChangeTrackingSupported(isManualChangeTrackingSupported())
                 .build();
     }
 
@@ -421,6 +445,49 @@
         }
     }
 
+    private boolean areNotificationsSupported() {
+        return mServerFlags.getBoolean(
+                ServerFlags.KEY_TIME_ZONE_NOTIFICATIONS_SUPPORTED,
+                getConfigBoolean(R.bool.config_enableTimeZoneNotificationsSupported));
+    }
+
+    private boolean isNotificationTrackingSupported() {
+        return mServerFlags.getBoolean(
+                ServerFlags.KEY_TIME_ZONE_NOTIFICATIONS_TRACKING_SUPPORTED,
+                getConfigBoolean(R.bool.config_enableTimeZoneNotificationsTrackingSupported));
+    }
+
+    private boolean isManualChangeTrackingSupported() {
+        return mServerFlags.getBoolean(
+                ServerFlags.KEY_TIME_ZONE_MANUAL_CHANGE_TRACKING_SUPPORTED,
+                getConfigBoolean(R.bool.config_enableTimeZoneManualChangeTrackingSupported));
+    }
+
+    private boolean getNotificationsEnabledSetting() {
+        final boolean notificationsEnabledByDefault = areNotificationsEnabledByDefault();
+        return Settings.Global.getInt(mCr, Settings.Global.TIME_ZONE_NOTIFICATIONS,
+                (notificationsEnabledByDefault ? 1 : 0) /* defaultValue */) != 0;
+    }
+
+    private boolean areNotificationsEnabledByDefault() {
+        return mServerFlags.getBoolean(
+                ServerFlags.KEY_TIME_ZONE_NOTIFICATIONS_ENABLED_DEFAULT, true);
+    }
+
+    private void setNotificationsEnabledSetting(boolean enabled) {
+        Settings.Global.putInt(mCr, Settings.Global.TIME_ZONE_NOTIFICATIONS, enabled ? 1 : 0);
+    }
+
+    private void setNotificationsEnabledIfRequired(boolean enabled) {
+        // This check is racey, but the whole settings update process is racey. This check prevents
+        // a ConfigurationChangeListener callback triggering due to ContentObserver's still
+        // triggering *sometimes* for no-op updates. Because callbacks are async this is necessary
+        // for stable behavior during tests.
+        if (getNotificationsEnabledSetting() != enabled) {
+            Settings.Global.putInt(mCr, Settings.Global.TIME_ZONE_NOTIFICATIONS, enabled ? 1 : 0);
+        }
+    }
+
     @Override
     public void addLocationTimeZoneManagerConfigListener(
             @NonNull StateChangeListener listener) {
@@ -441,8 +508,7 @@
 
     @Override
     public boolean isGeoTimeZoneDetectionFeatureSupportedInConfig() {
-        return mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_enableGeolocationTimeZoneDetection);
+        return getConfigBoolean(R.bool.config_enableGeolocationTimeZoneDetection);
     }
 
     @Override
@@ -660,8 +726,7 @@
     private boolean isTelephonyFallbackSupported() {
         return mServerFlags.getBoolean(
                 ServerFlags.KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED,
-                getConfigBoolean(
-                        com.android.internal.R.bool.config_supportTelephonyTimeZoneFallback));
+                getConfigBoolean(R.bool.config_supportTelephonyTimeZoneFallback));
     }
 
     private boolean getConfigBoolean(int providerEnabledConfigId) {
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneChangeListener.java b/services/core/java/com/android/server/timezonedetector/TimeZoneChangeListener.java
new file mode 100644
index 0000000..e14326c
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneChangeListener.java
@@ -0,0 +1,106 @@
+/*
+ * 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.timezonedetector;
+
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.ElapsedRealtimeLong;
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.util.IndentingPrintWriter;
+
+import com.android.server.SystemTimeZone.TimeZoneConfidence;
+import com.android.server.timezonedetector.TimeZoneDetectorStrategy.Origin;
+
+import java.util.Objects;
+
+public interface TimeZoneChangeListener {
+
+    /** Record a time zone change. */
+    void process(TimeZoneChangeEvent event);
+
+    /** Dump internal state. */
+    void dump(IndentingPrintWriter ipw);
+
+    class TimeZoneChangeEvent {
+
+        private final @ElapsedRealtimeLong long mElapsedRealtimeMillis;
+        private final @CurrentTimeMillisLong long mUnixEpochTimeMillis;
+        private final @Origin int mOrigin;
+        private final @UserIdInt int mUserId;
+        private final String mOldZoneId;
+        private final String mNewZoneId;
+        private final @TimeZoneConfidence int mNewConfidence;
+        private final String mCause;
+
+        public TimeZoneChangeEvent(@ElapsedRealtimeLong long elapsedRealtimeMillis,
+                @CurrentTimeMillisLong long unixEpochTimeMillis,
+                @Origin int origin, @UserIdInt int userId, @NonNull String oldZoneId,
+                @NonNull String newZoneId, int newConfidence, @NonNull String cause) {
+            mElapsedRealtimeMillis = elapsedRealtimeMillis;
+            mUnixEpochTimeMillis = unixEpochTimeMillis;
+            mOrigin = origin;
+            mUserId = userId;
+            mOldZoneId = Objects.requireNonNull(oldZoneId);
+            mNewZoneId = Objects.requireNonNull(newZoneId);
+            mNewConfidence = newConfidence;
+            mCause = Objects.requireNonNull(cause);
+        }
+
+        public @ElapsedRealtimeLong long getElapsedRealtimeMillis() {
+            return mElapsedRealtimeMillis;
+        }
+
+        public @CurrentTimeMillisLong long getUnixEpochTimeMillis() {
+            return mUnixEpochTimeMillis;
+        }
+
+        public @Origin int getOrigin() {
+            return mOrigin;
+        }
+
+        /**
+         * The ID of the user that triggered the change.
+         *
+         * <p>If automatic time zone is turned on, the user ID returned is the system's user id.
+         */
+        public @UserIdInt int getUserId() {
+            return mUserId;
+        }
+
+        public String getOldZoneId() {
+            return mOldZoneId;
+        }
+
+        public String getNewZoneId() {
+            return mNewZoneId;
+        }
+
+        @Override
+        public String toString() {
+            return "TimeZoneChangeEvent{"
+                    + "mElapsedRealtimeMillis=" + mElapsedRealtimeMillis
+                    + ", mUnixEpochTimeMillis=" + mUnixEpochTimeMillis
+                    + ", mOrigin=" + mOrigin
+                    + ", mUserId=" + mUserId
+                    + ", mOldZoneId='" + mOldZoneId + '\''
+                    + ", mNewZoneId='" + mNewZoneId + '\''
+                    + ", mNewConfidence=" + mNewConfidence
+                    + ", mCause='" + mCause + '\''
+                    + '}';
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index d914544..af02ad8 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.UserIdInt;
 import android.app.time.ITimeZoneDetectorListener;
 import android.app.time.TimeZoneCapabilitiesAndConfig;
@@ -73,6 +74,7 @@
         }
 
         @Override
+        @RequiresPermission("android.permission.INTERACT_ACROSS_USERS_FULL")
         public void onStart() {
             // Obtain / create the shared dependencies.
             Context context = getContext();
@@ -81,7 +83,7 @@
             ServiceConfigAccessor serviceConfigAccessor =
                     ServiceConfigAccessorImpl.getInstance(context);
             TimeZoneDetectorStrategy timeZoneDetectorStrategy =
-                    TimeZoneDetectorStrategyImpl.create(handler, serviceConfigAccessor);
+                    TimeZoneDetectorStrategyImpl.create(context, handler, serviceConfigAccessor);
             DeviceActivityMonitor deviceActivityMonitor =
                     DeviceActivityMonitorImpl.create(context, handler);
 
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
index 37e67c9..8cfbe9d 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.timezonedetector;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.app.time.TimeZoneCapabilitiesAndConfig;
@@ -24,6 +25,11 @@
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
 import android.util.IndentingPrintWriter;
 
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
 /**
  * The interface for the class that is responsible for setting the time zone on a device, used by
  * {@link TimeZoneDetectorService} and {@link TimeZoneDetectorInternal}.
@@ -97,6 +103,22 @@
  * @hide
  */
 public interface TimeZoneDetectorStrategy extends Dumpable {
+    @IntDef({ ORIGIN_UNKNOWN, ORIGIN_MANUAL, ORIGIN_TELEPHONY, ORIGIN_LOCATION })
+    @Retention(RetentionPolicy.SOURCE)
+    @Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER })
+    @interface Origin {}
+
+    /** Used when the origin of the time zone value cannot be inferred. */
+    @Origin int ORIGIN_UNKNOWN = 0;
+
+    /** Used when a time zone value originated from a user / manual settings. */
+    @Origin int ORIGIN_MANUAL = 1;
+
+    /** Used when a time zone value originated from a telephony signal. */
+    @Origin int ORIGIN_TELEPHONY = 2;
+
+    /** Used when a time zone value originated from a location signal. */
+    @Origin int ORIGIN_LOCATION = 3;
 
     /**
      * Adds a listener that will be triggered when something changes that could affect the result
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
index dddb46f..19a28dd 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
@@ -28,6 +28,7 @@
 import android.annotation.ElapsedRealtimeLong;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.UserIdInt;
 import android.app.time.DetectorStatusTypes;
 import android.app.time.LocationTimeZoneAlgorithmStatus;
@@ -39,8 +40,10 @@
 import android.app.time.TimeZoneState;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
+import android.content.Context;
 import android.os.Handler;
 import android.os.TimestampedValue;
+import android.os.UserHandle;
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
 
@@ -72,12 +75,14 @@
         /**
          * Returns the device's currently configured time zone. May return an empty string.
          */
-        @NonNull String getDeviceTimeZone();
+        @NonNull
+        String getDeviceTimeZone();
 
         /**
          * Returns the confidence of the device's current time zone.
          */
-        @TimeZoneConfidence int getDeviceTimeZoneConfidence();
+        @TimeZoneConfidence
+        int getDeviceTimeZoneConfidence();
 
         /**
          * Sets the device's time zone, associated confidence, and records a debug log entry.
@@ -115,7 +120,7 @@
     /**
      * The abstract score for an empty or invalid telephony suggestion.
      *
-     * Used to score telephony suggestions where there is no zone.
+     * <p>Used to score telephony suggestions where there is no zone.
      */
     @VisibleForTesting
     public static final int TELEPHONY_SCORE_NONE = 0;
@@ -123,11 +128,11 @@
     /**
      * The abstract score for a low quality telephony suggestion.
      *
-     * Used to score suggestions where:
-     * The suggested zone ID is one of several possibilities, and the possibilities have different
-     * offsets.
+     * <p>Used to score suggestions where:
+     * The suggested zone ID is one of several possibilities,
+     * and the possibilities have different offsets.
      *
-     * You would have to be quite desperate to want to use this choice.
+     * <p>You would have to be quite desperate to want to use this choice.
      */
     @VisibleForTesting
     public static final int TELEPHONY_SCORE_LOW = 1;
@@ -135,7 +140,7 @@
     /**
      * The abstract score for a medium quality telephony suggestion.
      *
-     * Used for:
+     * <p>Used for:
      * The suggested zone ID is one of several possibilities but at least the possibilities have the
      * same offset. Users would get the correct time but for the wrong reason. i.e. their device may
      * switch to DST at the wrong time and (for example) their calendar events.
@@ -146,7 +151,7 @@
     /**
      * The abstract score for a high quality telephony suggestion.
      *
-     * Used for:
+     * <p>Used for:
      * The suggestion was for one zone ID and the answer was unambiguous and likely correct given
      * the info available.
      */
@@ -156,7 +161,7 @@
     /**
      * The abstract score for a highest quality telephony suggestion.
      *
-     * Used for:
+     * <p>Used for:
      * Suggestions that must "win" because they constitute test or emulator zone ID.
      */
     @VisibleForTesting
@@ -206,7 +211,8 @@
     private final ServiceConfigAccessor mServiceConfigAccessor;
 
     @GuardedBy("this")
-    @NonNull private final List<StateChangeListener> mStateChangeListeners = new ArrayList<>();
+    @NonNull
+    private final List<StateChangeListener> mStateChangeListeners = new ArrayList<>();
 
     /**
      * A snapshot of the current detector status. A local copy is cached because it is relatively
@@ -244,8 +250,10 @@
     /**
      * Creates a new instance of {@link TimeZoneDetectorStrategyImpl}.
      */
+    @RequiresPermission("android.permission.INTERACT_ACROSS_USERS_FULL")
     public static TimeZoneDetectorStrategyImpl create(
-            @NonNull Handler handler, @NonNull ServiceConfigAccessor serviceConfigAccessor) {
+            @NonNull Context context, @NonNull Handler handler,
+            @NonNull ServiceConfigAccessor serviceConfigAccessor) {
 
         Environment environment = new EnvironmentImpl(handler);
         return new TimeZoneDetectorStrategyImpl(serviceConfigAccessor, environment);
@@ -468,7 +476,7 @@
         // later disables automatic time zone detection.
         mLatestManualSuggestion.set(suggestion);
 
-        setDeviceTimeZoneIfRequired(timeZoneId, cause);
+        setDeviceTimeZoneIfRequired(timeZoneId, ORIGIN_MANUAL, userId, cause);
         return true;
     }
 
@@ -685,7 +693,7 @@
 
         // GeolocationTimeZoneSuggestion has no measure of quality. We assume all suggestions are
         // reliable.
-        String zoneId;
+        String timeZoneId;
 
         // Introduce bias towards the device's current zone when there are multiple zone suggested.
         String deviceTimeZone = mEnvironment.getDeviceTimeZone();
@@ -694,11 +702,12 @@
                 Slog.d(LOG_TAG,
                         "Geo tz suggestion contains current device time zone. Applying bias.");
             }
-            zoneId = deviceTimeZone;
+            timeZoneId = deviceTimeZone;
         } else {
-            zoneId = zoneIds.get(0);
+            timeZoneId = zoneIds.get(0);
         }
-        setDeviceTimeZoneIfRequired(zoneId, detectionReason);
+        setDeviceTimeZoneIfRequired(timeZoneId, ORIGIN_LOCATION, UserHandle.USER_SYSTEM,
+                detectionReason);
         return true;
     }
 
@@ -779,8 +788,8 @@
 
         // Paranoia: Every suggestion above the SCORE_USAGE_THRESHOLD should have a non-null time
         // zone ID.
-        String zoneId = bestTelephonySuggestion.suggestion.getZoneId();
-        if (zoneId == null) {
+        String timeZoneId = bestTelephonySuggestion.suggestion.getZoneId();
+        if (timeZoneId == null) {
             Slog.w(LOG_TAG, "Empty zone suggestion scored higher than expected. This is an error:"
                     + " bestTelephonySuggestion=" + bestTelephonySuggestion
                     + ", detectionReason=" + detectionReason);
@@ -790,11 +799,12 @@
         String cause = "Found good suggestion:"
                 + " bestTelephonySuggestion=" + bestTelephonySuggestion
                 + ", detectionReason=" + detectionReason;
-        setDeviceTimeZoneIfRequired(zoneId, cause);
+        setDeviceTimeZoneIfRequired(timeZoneId, ORIGIN_TELEPHONY, UserHandle.USER_SYSTEM, cause);
     }
 
     @GuardedBy("this")
-    private void setDeviceTimeZoneIfRequired(@NonNull String newZoneId, @NonNull String cause) {
+    private void setDeviceTimeZoneIfRequired(@NonNull String newZoneId, @Origin int origin,
+            @UserIdInt int userId, @NonNull String cause) {
         String currentZoneId = mEnvironment.getDeviceTimeZone();
         // All manual and automatic suggestions are considered high confidence as low-quality
         // suggestions are not currently passed on.
diff --git a/services/core/java/com/android/server/vibrator/BasicToPwleSegmentAdapter.java b/services/core/java/com/android/server/vibrator/BasicToPwleSegmentAdapter.java
index 54ae047..0b676ff 100644
--- a/services/core/java/com/android/server/vibrator/BasicToPwleSegmentAdapter.java
+++ b/services/core/java/com/android/server/vibrator/BasicToPwleSegmentAdapter.java
@@ -100,6 +100,11 @@
         }
 
         VibratorInfo.FrequencyProfile frequencyProfile = info.getFrequencyProfile();
+        if (frequencyProfile.isEmpty()) {
+            // The frequency profile has an invalid frequency range, so keep the segments unchanged.
+            return repeatIndex;
+        }
+
         float[] frequenciesHz = frequencyProfile.getFrequenciesHz();
         float[] accelerationsGs = frequencyProfile.getOutputAccelerationsGs();
 
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 89c7a3d..6f308aa 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -1631,7 +1631,7 @@
 
         int positionToLog = APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__NOT_LETTERBOXED_POSITION;
         if (isAppCompateStateChangedToLetterboxed(state)) {
-            positionToLog = activity.mAppCompatController.getAppCompatReachabilityOverrides()
+            positionToLog = activity.mAppCompatController.getReachabilityOverrides()
                     .getLetterboxPositionForLogging();
         }
         FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPAT_STATE_CHANGED,
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 29f1f93..3d53078 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4261,7 +4261,7 @@
     }
 
     void finishRelaunching() {
-        mAppCompatController.getAppCompatOrientationOverrides()
+        mAppCompatController.getOrientationOverrides()
                 .setRelaunchingAfterRequestedOrientationChanged(false);
         mTaskSupervisor.getActivityMetricsLogger().notifyActivityRelaunched(this);
 
@@ -8222,7 +8222,7 @@
                 mLastReportedConfiguration.getMergedConfiguration())) {
             ensureActivityConfiguration(false /* ignoreVisibility */);
             if (mPendingRelaunchCount > originalRelaunchingCount) {
-                mAppCompatController.getAppCompatOrientationOverrides()
+                mAppCompatController.getOrientationOverrides()
                         .setRelaunchingAfterRequestedOrientationChanged(true);
             }
             if (mTransitionController.inPlayingTransition(this)) {
@@ -8744,7 +8744,7 @@
             navBarInsets = Insets.NONE;
         }
         final AppCompatReachabilityOverrides reachabilityOverrides =
-                mAppCompatController.getAppCompatReachabilityOverrides();
+                mAppCompatController.getReachabilityOverrides();
         // Horizontal position
         int offsetX = 0;
         if (parentBounds.width() != screenResolvedBoundsWidth) {
@@ -10217,7 +10217,7 @@
                 mAppCompatController.getAppCompatAspectRatioOverrides()
                         .shouldOverrideMinAspectRatio());
         proto.write(SHOULD_IGNORE_ORIENTATION_REQUEST_LOOP,
-                mAppCompatController.getAppCompatOrientationOverrides()
+                mAppCompatController.getOrientationOverrides()
                         .shouldIgnoreOrientationRequestLoop());
         proto.write(SHOULD_OVERRIDE_FORCE_RESIZE_APP,
                 mAppCompatController.getResizeOverrides().shouldOverrideForceResizeApp());
diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index 0967078..6d0e8ea 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -33,7 +33,7 @@
     @NonNull
     private final AppCompatAspectRatioPolicy mAppCompatAspectRatioPolicy;
     @NonNull
-    private final AppCompatReachabilityPolicy mAppCompatReachabilityPolicy;
+    private final AppCompatReachabilityPolicy mReachabilityPolicy;
     @NonNull
     private final DesktopAppCompatAspectRatioPolicy mDesktopAppCompatAspectRatioPolicy;
     @NonNull
@@ -58,7 +58,7 @@
         mOrientationPolicy = new AppCompatOrientationPolicy(activityRecord, mAppCompatOverrides);
         mAppCompatAspectRatioPolicy = new AppCompatAspectRatioPolicy(activityRecord,
                 mTransparentPolicy, mAppCompatOverrides);
-        mAppCompatReachabilityPolicy = new AppCompatReachabilityPolicy(activityRecord,
+        mReachabilityPolicy = new AppCompatReachabilityPolicy(activityRecord,
                 wmService.mAppCompatConfiguration);
         mAppCompatLetterboxPolicy = new AppCompatLetterboxPolicy(activityRecord,
                 wmService.mAppCompatConfiguration);
@@ -89,8 +89,8 @@
     }
 
     @NonNull
-    AppCompatOrientationOverrides getAppCompatOrientationOverrides() {
-        return mAppCompatOverrides.getAppCompatOrientationOverrides();
+    AppCompatOrientationOverrides getOrientationOverrides() {
+        return mAppCompatOverrides.getOrientationOverrides();
     }
 
     @NonNull
@@ -109,8 +109,8 @@
     }
 
     @NonNull
-    AppCompatReachabilityPolicy getAppCompatReachabilityPolicy() {
-        return mAppCompatReachabilityPolicy;
+    AppCompatReachabilityPolicy getReachabilityPolicy() {
+        return mReachabilityPolicy;
     }
 
     @NonNull
@@ -124,8 +124,8 @@
     }
 
     @NonNull
-    AppCompatReachabilityOverrides getAppCompatReachabilityOverrides() {
-        return mAppCompatOverrides.getAppCompatReachabilityOverrides();
+    AppCompatReachabilityOverrides getReachabilityOverrides() {
+        return mAppCompatOverrides.getReachabilityOverrides();
     }
 
     @NonNull
diff --git a/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
index e929fb4..4494586 100644
--- a/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatLetterboxPolicy.java
@@ -154,7 +154,7 @@
 
     @VisibleForTesting
     boolean shouldShowLetterboxUi(@NonNull WindowState mainWindow) {
-        if (mActivityRecord.mAppCompatController.getAppCompatOrientationOverrides()
+        if (mActivityRecord.mAppCompatController.getOrientationOverrides()
                 .getIsRelaunchingAfterRequestedOrientationChanged()) {
             return mLastShouldShowLetterboxUi;
         }
@@ -205,7 +205,7 @@
         }
         pw.println(prefix + "  letterboxReason="
                 + AppCompatUtils.getLetterboxReasonString(mActivityRecord, mainWin));
-        mActivityRecord.mAppCompatController.getAppCompatReachabilityPolicy().dump(pw, prefix);
+        mActivityRecord.mAppCompatController.getReachabilityPolicy().dump(pw, prefix);
         final AppCompatLetterboxOverrides letterboxOverride = mActivityRecord.mAppCompatController
                 .getAppCompatLetterboxOverrides();
         pw.println(prefix + "  letterboxBackgroundColor=" + Integer.toHexString(
@@ -276,12 +276,12 @@
                 final AppCompatLetterboxOverrides letterboxOverrides = mActivityRecord
                         .mAppCompatController.getAppCompatLetterboxOverrides();
                 final AppCompatReachabilityPolicy reachabilityPolicy = mActivityRecord
-                        .mAppCompatController.getAppCompatReachabilityPolicy();
+                        .mAppCompatController.getReachabilityPolicy();
                 mLetterbox = new Letterbox(() -> mActivityRecord.makeChildSurface(null),
                         mActivityRecord.mWmService.mTransactionFactory,
                         reachabilityPolicy, letterboxOverrides,
                         this::getLetterboxParentSurface);
-                mActivityRecord.mAppCompatController.getAppCompatReachabilityPolicy()
+                mActivityRecord.mAppCompatController.getReachabilityPolicy()
                         .setLetterboxInnerBoundsSupplier(mLetterbox::getInnerFrame);
             }
             final Point letterboxPosition = new Point();
@@ -291,7 +291,7 @@
             final Rect innerFrame = new Rect();
             calculateLetterboxInnerBounds(mActivityRecord, w, innerFrame);
             mLetterbox.layout(spaceToFill, innerFrame, letterboxPosition);
-            if (mActivityRecord.mAppCompatController.getAppCompatReachabilityOverrides()
+            if (mActivityRecord.mAppCompatController.getReachabilityOverrides()
                     .isDoubleTapEvent()) {
                 // We need to notify Shell that letterbox position has changed.
                 mActivityRecord.getTask().dispatchTaskInfoChangedIfNeeded(true /* force */);
@@ -321,7 +321,7 @@
                 mLetterbox.destroy();
                 mLetterbox = null;
             }
-            mActivityRecord.mAppCompatController.getAppCompatReachabilityPolicy()
+            mActivityRecord.mAppCompatController.getReachabilityPolicy()
                     .setLetterboxInnerBoundsSupplier(null);
         }
 
@@ -415,7 +415,7 @@
             calculateLetterboxPosition(mActivityRecord, mLetterboxPosition);
             calculateLetterboxOuterBounds(mActivityRecord, mOuterBounds);
             calculateLetterboxInnerBounds(mActivityRecord, w, mInnerBounds);
-            mActivityRecord.mAppCompatController.getAppCompatReachabilityPolicy()
+            mActivityRecord.mAppCompatController.getReachabilityPolicy()
                     .setLetterboxInnerBoundsSupplier(() -> mInnerBounds);
         }
 
@@ -438,7 +438,7 @@
             mLetterboxPosition.set(0, 0);
             mInnerBounds.setEmpty();
             mOuterBounds.setEmpty();
-            mActivityRecord.mAppCompatController.getAppCompatReachabilityPolicy()
+            mActivityRecord.mAppCompatController.getReachabilityPolicy()
                     .setLetterboxInnerBoundsSupplier(null);
         }
 
diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java b/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java
index c84711d..af83668 100644
--- a/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java
@@ -113,7 +113,7 @@
         // Task to ensure that Activity Embedding is excluded.
         return mActivityRecord.isVisibleRequested() && mActivityRecord.getTaskFragment() != null
                 && mActivityRecord.getTaskFragment().getWindowingMode() == WINDOWING_MODE_FULLSCREEN
-                && mActivityRecord.mAppCompatController.getAppCompatOrientationOverrides()
+                && mActivityRecord.mAppCompatController.getOrientationOverrides()
                     .isOverrideRespectRequestedOrientationEnabled();
     }
 
diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
index 16e2029..fc758ef 100644
--- a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
@@ -94,7 +94,7 @@
             return SCREEN_ORIENTATION_PORTRAIT;
         }
 
-        if (mAppCompatOverrides.getAppCompatOrientationOverrides()
+        if (mAppCompatOverrides.getOrientationOverrides()
                 .isAllowOrientationOverrideOptOut()) {
             return candidate;
         }
@@ -108,7 +108,7 @@
         }
 
         final AppCompatOrientationOverrides.OrientationOverridesState capabilityState =
-                mAppCompatOverrides.getAppCompatOrientationOverrides()
+                mAppCompatOverrides.getOrientationOverrides()
                         .mOrientationOverridesState;
 
         if (capabilityState.mIsOverrideToReverseLandscapeOrientationEnabled
@@ -170,7 +170,7 @@
     boolean shouldIgnoreRequestedOrientation(
             @ActivityInfo.ScreenOrientation int requestedOrientation) {
         final AppCompatOrientationOverrides orientationOverrides =
-                mAppCompatOverrides.getAppCompatOrientationOverrides();
+                mAppCompatOverrides.getOrientationOverrides();
         if (orientationOverrides.shouldEnableIgnoreOrientationRequest()) {
             if (orientationOverrides.getIsRelaunchingAfterRequestedOrientationChanged()) {
                 Slog.w(TAG, "Ignoring orientation update to "
diff --git a/services/core/java/com/android/server/wm/AppCompatOverrides.java b/services/core/java/com/android/server/wm/AppCompatOverrides.java
index 58b37be..9fb54db 100644
--- a/services/core/java/com/android/server/wm/AppCompatOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatOverrides.java
@@ -27,7 +27,7 @@
 public class AppCompatOverrides {
 
     @NonNull
-    private final AppCompatOrientationOverrides mAppCompatOrientationOverrides;
+    private final AppCompatOrientationOverrides mOrientationOverrides;
     @NonNull
     private final AppCompatCameraOverrides mAppCompatCameraOverrides;
     @NonNull
@@ -37,7 +37,7 @@
     @NonNull
     private final AppCompatResizeOverrides mResizeOverrides;
     @NonNull
-    private final AppCompatReachabilityOverrides mAppCompatReachabilityOverrides;
+    private final AppCompatReachabilityOverrides mReachabilityOverrides;
     @NonNull
     private final AppCompatLetterboxOverrides mAppCompatLetterboxOverrides;
 
@@ -48,13 +48,13 @@
             @NonNull AppCompatDeviceStateQuery appCompatDeviceStateQuery) {
         mAppCompatCameraOverrides = new AppCompatCameraOverrides(activityRecord,
                 appCompatConfiguration, optPropBuilder);
-        mAppCompatOrientationOverrides = new AppCompatOrientationOverrides(activityRecord,
+        mOrientationOverrides = new AppCompatOrientationOverrides(activityRecord,
                 appCompatConfiguration, optPropBuilder, mAppCompatCameraOverrides);
-        mAppCompatReachabilityOverrides = new AppCompatReachabilityOverrides(activityRecord,
+        mReachabilityOverrides = new AppCompatReachabilityOverrides(activityRecord,
                 appCompatConfiguration, appCompatDeviceStateQuery);
         mAppCompatAspectRatioOverrides = new AppCompatAspectRatioOverrides(activityRecord,
                 appCompatConfiguration, optPropBuilder, appCompatDeviceStateQuery,
-                mAppCompatReachabilityOverrides);
+                mReachabilityOverrides);
         mAppCompatFocusOverrides = new AppCompatFocusOverrides(activityRecord,
                 appCompatConfiguration, optPropBuilder);
         mResizeOverrides = new AppCompatResizeOverrides(activityRecord, packageManager,
@@ -64,8 +64,8 @@
     }
 
     @NonNull
-    AppCompatOrientationOverrides getAppCompatOrientationOverrides() {
-        return mAppCompatOrientationOverrides;
+    AppCompatOrientationOverrides getOrientationOverrides() {
+        return mOrientationOverrides;
     }
 
     @NonNull
@@ -89,8 +89,8 @@
     }
 
     @NonNull
-    AppCompatReachabilityOverrides getAppCompatReachabilityOverrides() {
-        return mAppCompatReachabilityOverrides;
+    AppCompatReachabilityOverrides getReachabilityOverrides() {
+        return mReachabilityOverrides;
     }
 
     @NonNull
diff --git a/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java b/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java
index d03a803..087edc1 100644
--- a/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatReachabilityPolicy.java
@@ -77,7 +77,7 @@
 
     void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
         final AppCompatReachabilityOverrides reachabilityOverrides =
-                mActivityRecord.mAppCompatController.getAppCompatReachabilityOverrides();
+                mActivityRecord.mAppCompatController.getReachabilityOverrides();
         pw.println(prefix + "  isVerticalThinLetterboxed=" + reachabilityOverrides
                 .isVerticalThinLetterboxed());
         pw.println(prefix + "  isHorizontalThinLetterboxed=" + reachabilityOverrides
@@ -96,7 +96,7 @@
 
     private void handleHorizontalDoubleTap(int x) {
         final AppCompatReachabilityOverrides reachabilityOverrides =
-                mActivityRecord.mAppCompatController.getAppCompatReachabilityOverrides();
+                mActivityRecord.mAppCompatController.getReachabilityOverrides();
         if (!reachabilityOverrides.isHorizontalReachabilityEnabled()
                 || mActivityRecord.isInTransition()) {
             return;
@@ -142,7 +142,7 @@
 
     private void handleVerticalDoubleTap(int y) {
         final AppCompatReachabilityOverrides reachabilityOverrides =
-                mActivityRecord.mAppCompatController.getAppCompatReachabilityOverrides();
+                mActivityRecord.mAppCompatController.getReachabilityOverrides();
         if (!reachabilityOverrides.isVerticalReachabilityEnabled()
                 || mActivityRecord.isInTransition()) {
             return;
diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java
index 9f88bc9..e28dddc 100644
--- a/services/core/java/com/android/server/wm/AppCompatUtils.java
+++ b/services/core/java/com/android/server/wm/AppCompatUtils.java
@@ -138,7 +138,7 @@
             return;
         }
         final AppCompatReachabilityOverrides reachabilityOverrides = top.mAppCompatController
-                .getAppCompatReachabilityOverrides();
+                .getReachabilityOverrides();
         final boolean isTopActivityResumed = top.getOrganizedTask() == task && top.isState(RESUMED);
         final boolean isTopActivityVisible = top.getOrganizedTask() == task && top.isVisible();
         // Whether the direct top activity is in size compat mode.
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index a4e58ef..d6ae651 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -108,9 +108,7 @@
 
     ContentRecorder(@NonNull DisplayContent displayContent) {
         this(displayContent, new RemoteMediaProjectionManagerWrapper(displayContent.mDisplayId),
-                new DisplayManagerFlags().isConnectedDisplayManagementEnabled()
-                        && !new DisplayManagerFlags()
-                                    .isPixelAnisotropyCorrectionInLogicalDisplayEnabled()
+                !new DisplayManagerFlags().isPixelAnisotropyCorrectionInLogicalDisplayEnabled()
                         && displayContent.getDisplayInfo().type == Display.TYPE_EXTERNAL);
     }
 
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index f40d636..b932ef3 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -264,7 +264,7 @@
         // that should be respected, Check all activities in display to make sure any eligible
         // activity should be respected.
         final ActivityRecord activity = mDisplayContent.getActivity((r) ->
-                r.mAppCompatController.getAppCompatOrientationOverrides()
+                r.mAppCompatController.getOrientationOverrides()
                     .shouldRespectRequestedOrientationDueToOverride());
         return activity != null;
     }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 145c7b3..dd23f57 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -2964,7 +2964,7 @@
         if (!handlesOrientationChangeFromDescendant(orientation)) {
             ActivityRecord topActivity = topRunningActivity(/* considerKeyguardState= */ true);
             if (topActivity != null && topActivity.mAppCompatController
-                    .getAppCompatOrientationOverrides()
+                    .getOrientationOverrides()
                         .shouldUseDisplayLandscapeNaturalOrientation()) {
                 ProtoLog.v(WM_DEBUG_ORIENTATION,
                         "Display id=%d is ignoring orientation request for %d, return %d"
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 3c6778e..92e0931 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -10177,9 +10177,10 @@
             throw new SecurityException("Access denied to process: " + pid
                     + ", must have permission " + Manifest.permission.ACCESS_FPS_COUNTER);
         }
-
-        if (mRoot.anyTaskForId(taskId) == null) {
-            throw new IllegalArgumentException("no task with taskId: " + taskId);
+        synchronized (mGlobalLock) {
+            if (mRoot.anyTaskForId(taskId) == null) {
+                throw new IllegalArgumentException("no task with taskId: " + taskId);
+            }
         }
 
         mTaskFpsCallbackController.registerListener(taskId, callback);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index b43e334..3a2a1ea 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5424,7 +5424,7 @@
             // change then delay the position update until it has redrawn to avoid any flickers.
             final boolean isLetterboxedAndRelaunching = activityRecord != null
                     && activityRecord.areBoundsLetterboxed()
-                    && activityRecord.mAppCompatController.getAppCompatOrientationOverrides()
+                    && activityRecord.mAppCompatController.getOrientationOverrides()
                         .getIsRelaunchingAfterRequestedOrientationChanged();
             if (surfaceResizedWithoutMoveAnimation || isLetterboxedAndRelaunching) {
                 applyWithNextDraw(mSetSurfacePositionConsumer);
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 a9ad435..02e5470 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -415,7 +415,6 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(false);
 
         mLocalServiceKeeperRule.overrideLocalService(
                 InputManagerInternal.class, mMockInputManagerInternal);
@@ -2797,30 +2796,7 @@
     }
 
     @Test
-    public void testConnectExternalDisplay_withoutDisplayManagement_shouldAddDisplay() {
-        when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(false);
-        manageDisplaysPermission(/* granted= */ true);
-        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
-        DisplayManagerService.BinderService bs = displayManager.new BinderService();
-        LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
-        FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
-        bs.registerCallbackWithEventMask(callback, STANDARD_AND_CONNECTION_DISPLAY_EVENTS);
-        callback.expectsEvent(EVENT_DISPLAY_ADDED);
-
-        FakeDisplayDevice displayDevice =
-                createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL);
-        callback.waitForExpectedEvent();
-
-        LogicalDisplay display =
-                logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
-        assertThat(display.isEnabledLocked()).isTrue();
-        assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_ADDED);
-
-    }
-
-    @Test
-    public void testConnectExternalDisplay_withDisplayManagement_shouldDisableDisplay() {
-        when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
+    public void testConnectExternalDisplay_shouldDisableDisplay() {
         manageDisplaysPermission(/* granted= */ true);
         DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
         displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
@@ -2849,9 +2825,8 @@
     }
 
     @Test
-    public void testConnectExternalDisplay_withDisplayManagementAndSysprop_shouldEnableDisplay() {
+    public void testConnectExternalDisplay_withSysprop_shouldEnableDisplay() {
         Assume.assumeTrue(Build.IS_ENG || Build.IS_USERDEBUG);
-        when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
         doAnswer((Answer<Boolean>) invocationOnMock -> true)
                 .when(() -> SystemProperties.getBoolean(ENABLE_ON_CONNECT, false));
         manageDisplaysPermission(/* granted= */ true);
@@ -2883,8 +2858,7 @@
     }
 
     @Test
-    public void testConnectExternalDisplay_withDisplayManagement_allowsEnableAndDisableDisplay() {
-        when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
+    public void testConnectExternalDisplay_allowsEnableAndDisableDisplay() {
         when(mMockFlags.isApplyDisplayChangedDuringDisplayAddedEnabled()).thenReturn(true);
         manageDisplaysPermission(/* granted= */ true);
         LocalServices.addService(WindowManagerPolicy.class, mMockedWindowManagerPolicy);
@@ -2955,8 +2929,7 @@
     }
 
     @Test
-    public void testConnectInternalDisplay_withDisplayManagement_shouldConnectAndAddDisplay() {
-        when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
+    public void testConnectInternalDisplay_shouldConnectAndAddDisplay() {
         manageDisplaysPermission(/* granted= */ true);
         DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
         DisplayManagerService.BinderService bs = displayManager.new BinderService();
@@ -3011,7 +2984,7 @@
         DisplayManagerService.BinderService bs = displayManager.new BinderService();
         LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
         FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
-        bs.registerCallbackWithEventMask(callback, STANDARD_AND_CONNECTION_DISPLAY_EVENTS);
+        bs.registerCallbackWithEventMask(callback, STANDARD_DISPLAY_EVENTS);
 
         callback.expectsEvent(EVENT_DISPLAY_ADDED);
         FakeDisplayDevice displayDevice =
@@ -3032,8 +3005,7 @@
     }
 
     @Test
-    public void testEnableExternalDisplay_withDisplayManagement_shouldSignalDisplayAdded() {
-        when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
+    public void testEnableExternalDisplay_shouldSignalDisplayAdded() {
         manageDisplaysPermission(/* granted= */ true);
         DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
         displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
@@ -3062,8 +3034,7 @@
     }
 
     @Test
-    public void testEnableExternalDisplay_withoutPermission_shouldThrowException() {
-        when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
+    public void testEnableExternalDisplay_shouldThrowException() {
         DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
         DisplayManagerService.BinderService bs = displayManager.new BinderService();
         LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
@@ -3087,8 +3058,7 @@
     }
 
     @Test
-    public void testEnableInternalDisplay_withManageDisplays_shouldSignalAdded() {
-        when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
+    public void testEnableInternalDisplay_shouldSignalAdded() {
         DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
         DisplayManagerService.BinderService bs = displayManager.new BinderService();
         LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
@@ -3115,8 +3085,7 @@
     }
 
     @Test
-    public void testDisableInternalDisplay_withDisplayManagement_shouldSignalRemove() {
-        when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
+    public void testDisableInternalDisplay_shouldSignalRemove() {
         DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
         DisplayManagerService.BinderService bs = displayManager.new BinderService();
         LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
@@ -3140,7 +3109,6 @@
 
     @Test
     public void testDisableExternalDisplay_shouldSignalDisplayRemoved() {
-        when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
         DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
         DisplayManagerService.BinderService bs = displayManager.new BinderService();
         LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
@@ -3181,7 +3149,6 @@
 
     @Test
     public void testDisableExternalDisplay_withoutPermission_shouldThrowException() {
-        when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
         DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
         DisplayManagerService.BinderService bs = displayManager.new BinderService();
         LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
@@ -3207,7 +3174,6 @@
 
     @Test
     public void testRemoveExternalDisplay_whenDisabled_shouldSignalDisconnected() {
-        when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
         manageDisplaysPermission(/* granted= */ true);
         DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
         displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
@@ -3244,7 +3210,6 @@
 
     @Test
     public void testRegisterCallback_withoutPermission_shouldThrow() {
-        when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
         DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
         DisplayManagerService.BinderService bs = displayManager.new BinderService();
         FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
@@ -3255,7 +3220,6 @@
 
     @Test
     public void testRemoveExternalDisplay_whenEnabled_shouldSignalRemovedAndDisconnected() {
-        when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
         manageDisplaysPermission(/* granted= */ true);
         DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
         displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
@@ -3288,7 +3252,6 @@
 
     @Test
     public void testRemoveInternalDisplay_whenEnabled_shouldSignalRemovedAndDisconnected() {
-        when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
         manageDisplaysPermission(/* granted= */ true);
         DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
         DisplayManagerService.BinderService bs = displayManager.new BinderService();
diff --git a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
index 782262d..a48a88c 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
@@ -22,7 +22,6 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.junit.Assume.assumeFalse;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -47,7 +46,6 @@
 import com.android.server.display.notifications.DisplayNotificationManager;
 import com.android.server.testutils.TestHandler;
 
-import com.google.testing.junit.testparameterinjector.TestParameter;
 import com.google.testing.junit.testparameterinjector.TestParameterInjector;
 
 import org.junit.Before;
@@ -124,7 +122,6 @@
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
         mHandler = new TestHandler(/*callback=*/ null);
-        when(mMockedFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
         when(mMockedFlags.isConnectedDisplayErrorHandlingEnabled()).thenReturn(true);
         when(mMockedInjector.getFlags()).thenReturn(mMockedFlags);
         when(mMockedInjector.getLogicalDisplayMapper()).thenReturn(mMockedLogicalDisplayMapper);
@@ -173,16 +170,6 @@
     }
 
     @Test
-    public void testTryEnableExternalDisplay_featureDisabled(@TestParameter final boolean enable) {
-        when(mMockedFlags.isConnectedDisplayManagementEnabled()).thenReturn(false);
-        mExternalDisplayPolicy.setExternalDisplayEnabledLocked(mMockedLogicalDisplay, enable);
-        mHandler.flush();
-        verify(mMockedLogicalDisplayMapper, never()).setDisplayEnabledLocked(any(), anyBoolean());
-        verify(mMockedDisplayNotificationManager, never())
-                .onHighTemperatureExternalDisplayNotAllowed();
-    }
-
-    @Test
     public void testTryDisableExternalDisplay_criticalThermalCondition() throws RemoteException {
         // Disallow external displays due to thermals.
         setTemperature(registerThermalListener(), List.of(CRITICAL_TEMPERATURE));
@@ -278,21 +265,6 @@
     }
 
     @Test
-    public void testNoThermalListenerRegistered_featureDisabled(
-            @TestParameter final boolean isConnectedDisplayManagementEnabled,
-            @TestParameter final boolean isErrorHandlingEnabled) throws RemoteException {
-        assumeFalse(isConnectedDisplayManagementEnabled && isErrorHandlingEnabled);
-        when(mMockedFlags.isConnectedDisplayManagementEnabled()).thenReturn(
-                isConnectedDisplayManagementEnabled);
-        when(mMockedFlags.isConnectedDisplayErrorHandlingEnabled()).thenReturn(
-                isErrorHandlingEnabled);
-
-        mExternalDisplayPolicy.onBootCompleted();
-        verify(mMockedThermalService, never()).registerThermalEventListenerWithType(
-                any(), anyInt());
-    }
-
-    @Test
     public void testOnCriticalTemperature_disallowAndAllowExternalDisplay() throws RemoteException {
         final var thermalListener = registerThermalListener();
 
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 0dbb6ba..7d3cd8a 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -222,7 +222,6 @@
         when(mSyntheticModeManagerMock.createAppSupportedModes(any(), any(), anyBoolean()))
                 .thenAnswer(AdditionalAnswers.returnsSecondArg());
 
-        when(mFlagsMock.isConnectedDisplayManagementEnabled()).thenReturn(false);
         mLooper = new TestLooper();
         mHandler = new Handler(mLooper.getLooper());
         mLogicalDisplayMapper = new LogicalDisplayMapper(mContextMock, mFoldSettingProviderMock,
@@ -351,8 +350,7 @@
     }
 
     @Test
-    public void testDisplayDeviceAddAndRemove_withDisplayManagement() {
-        when(mFlagsMock.isConnectedDisplayManagementEnabled()).thenReturn(true);
+    public void testDisplayDeviceAddAndRemove() {
         DisplayDevice device = createDisplayDevice(TYPE_INTERNAL, 600, 800,
                 FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
 
@@ -390,8 +388,7 @@
     }
 
     @Test
-    public void testDisplayDisableEnable_withDisplayManagement() {
-        when(mFlagsMock.isConnectedDisplayManagementEnabled()).thenReturn(true);
+    public void testDisplayDisableEnable() {
         DisplayDevice device = createDisplayDevice(TYPE_INTERNAL, 600, 800,
                 FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
         LogicalDisplay displayAdded = add(device);
@@ -1350,9 +1347,14 @@
         ArgumentCaptor<LogicalDisplay> displayCaptor =
                 ArgumentCaptor.forClass(LogicalDisplay.class);
         verify(mListenerMock).onLogicalDisplayEventLocked(
-                displayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_ADDED));
+                displayCaptor.capture(), eq(LOGICAL_DISPLAY_EVENT_CONNECTED));
+        LogicalDisplay display = displayCaptor.getValue();
+        if (display.isEnabledLocked()) {
+            verify(mListenerMock).onLogicalDisplayEventLocked(
+                    eq(display), eq(LOGICAL_DISPLAY_EVENT_ADDED));
+        }
         clearInvocations(mListenerMock);
-        return displayCaptor.getValue();
+        return display;
     }
 
     private void testDisplayDeviceAddAndRemove_NonInternal(int type) {
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 0d86d4c..60a4b9a 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -402,6 +402,7 @@
         });
     }
 
+    /**
     public void testPushDynamicShortcut() {
         // Change the max number of shortcuts.
         mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=5,"
@@ -543,6 +544,7 @@
         verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage(
                 eq(CALLING_PACKAGE_1), eq("s9"), eq(USER_10));
     }
+    */
 
     public void testPushDynamicShortcut_CallsToUsageStatsManagerAreThrottled()
             throws InterruptedException {
diff --git a/services/tests/timetests/src/com/android/server/timezonedetector/ConfigInternalForTests.java b/services/tests/timetests/src/com/android/server/timezonedetector/ConfigInternalForTests.java
new file mode 100644
index 0000000..47e3dc8
--- /dev/null
+++ b/services/tests/timetests/src/com/android/server/timezonedetector/ConfigInternalForTests.java
@@ -0,0 +1,108 @@
+/*
+ * 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.
+ */
+
+package com.android.server.timezonedetector;
+
+import android.annotation.UserIdInt;
+
+public final class ConfigInternalForTests {
+
+    static final @UserIdInt int USER_ID = 9876;
+
+    static final ConfigurationInternal CONFIG_USER_RESTRICTED_AUTO_DISABLED =
+            new ConfigurationInternal.Builder()
+                    .setUserId(USER_ID)
+                    .setTelephonyDetectionFeatureSupported(true)
+                    .setGeoDetectionFeatureSupported(true)
+                    .setTelephonyFallbackSupported(false)
+                    .setGeoDetectionRunInBackgroundEnabled(false)
+                    .setEnhancedMetricsCollectionEnabled(false)
+                    .setUserConfigAllowed(false)
+                    .setAutoDetectionEnabledSetting(false)
+                    .setLocationEnabledSetting(true)
+                    .setGeoDetectionEnabledSetting(false)
+                    .build();
+
+    static final ConfigurationInternal CONFIG_USER_RESTRICTED_AUTO_ENABLED =
+            new ConfigurationInternal.Builder()
+                    .setUserId(USER_ID)
+                    .setTelephonyDetectionFeatureSupported(true)
+                    .setGeoDetectionFeatureSupported(true)
+                    .setTelephonyFallbackSupported(false)
+                    .setGeoDetectionRunInBackgroundEnabled(false)
+                    .setEnhancedMetricsCollectionEnabled(false)
+                    .setUserConfigAllowed(false)
+                    .setAutoDetectionEnabledSetting(true)
+                    .setLocationEnabledSetting(true)
+                    .setGeoDetectionEnabledSetting(true)
+                    .build();
+
+    static final ConfigurationInternal CONFIG_AUTO_DETECT_NOT_SUPPORTED =
+            new ConfigurationInternal.Builder()
+                    .setUserId(USER_ID)
+                    .setTelephonyDetectionFeatureSupported(false)
+                    .setGeoDetectionFeatureSupported(false)
+                    .setTelephonyFallbackSupported(false)
+                    .setGeoDetectionRunInBackgroundEnabled(false)
+                    .setEnhancedMetricsCollectionEnabled(false)
+                    .setUserConfigAllowed(true)
+                    .setAutoDetectionEnabledSetting(false)
+                    .setLocationEnabledSetting(true)
+                    .setGeoDetectionEnabledSetting(false)
+                    .build();
+
+    static final ConfigurationInternal CONFIG_AUTO_DISABLED_GEO_DISABLED =
+            new ConfigurationInternal.Builder()
+                    .setUserId(USER_ID)
+                    .setTelephonyDetectionFeatureSupported(true)
+                    .setGeoDetectionFeatureSupported(true)
+                    .setTelephonyFallbackSupported(false)
+                    .setGeoDetectionRunInBackgroundEnabled(false)
+                    .setEnhancedMetricsCollectionEnabled(false)
+                    .setUserConfigAllowed(true)
+                    .setAutoDetectionEnabledSetting(false)
+                    .setLocationEnabledSetting(true)
+                    .setGeoDetectionEnabledSetting(false)
+                    .build();
+
+    static final ConfigurationInternal CONFIG_AUTO_ENABLED_GEO_DISABLED =
+            new ConfigurationInternal.Builder()
+                    .setUserId(USER_ID)
+                    .setTelephonyDetectionFeatureSupported(true)
+                    .setGeoDetectionFeatureSupported(true)
+                    .setTelephonyFallbackSupported(false)
+                    .setGeoDetectionRunInBackgroundEnabled(false)
+                    .setEnhancedMetricsCollectionEnabled(false)
+                    .setUserConfigAllowed(true)
+                    .setAutoDetectionEnabledSetting(true)
+                    .setLocationEnabledSetting(true)
+                    .setGeoDetectionEnabledSetting(false)
+                    .build();
+
+    static final ConfigurationInternal CONFIG_AUTO_ENABLED_GEO_ENABLED =
+            new ConfigurationInternal.Builder()
+                    .setUserId(USER_ID)
+                    .setTelephonyDetectionFeatureSupported(true)
+                    .setGeoDetectionFeatureSupported(true)
+                    .setTelephonyFallbackSupported(false)
+                    .setGeoDetectionRunInBackgroundEnabled(false)
+                    .setEnhancedMetricsCollectionEnabled(false)
+                    .setUserConfigAllowed(true)
+                    .setAutoDetectionEnabledSetting(true)
+                    .setLocationEnabledSetting(true)
+                    .setGeoDetectionEnabledSetting(true)
+                    .build();
+}
diff --git a/services/tests/timetests/src/com/android/server/timezonedetector/FakeServiceConfigAccessor.java b/services/tests/timetests/src/com/android/server/timezonedetector/FakeServiceConfigAccessor.java
index fc6afe4..aeb4d9a 100644
--- a/services/tests/timetests/src/com/android/server/timezonedetector/FakeServiceConfigAccessor.java
+++ b/services/tests/timetests/src/com/android/server/timezonedetector/FakeServiceConfigAccessor.java
@@ -31,7 +31,7 @@
 /**
  * A partially implemented, fake implementation of ServiceConfigAccessor for tests.
  *
- * <p>This class has rudamentary support for multiple users, but unlike the real thing, it doesn't
+ * <p>This class has rudimentary support for multiple users, but unlike the real thing, it doesn't
  * simulate that some settings are global and shared between users. It also delivers config updates
  * synchronously.
  */
diff --git a/services/tests/timetests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/timetests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
index e52e8b6..47a9b2c 100644
--- a/services/tests/timetests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/timetests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -35,6 +35,12 @@
 
 import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH;
 import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_LOW;
+import static com.android.server.timezonedetector.ConfigInternalForTests.CONFIG_AUTO_DETECT_NOT_SUPPORTED;
+import static com.android.server.timezonedetector.ConfigInternalForTests.CONFIG_AUTO_DISABLED_GEO_DISABLED;
+import static com.android.server.timezonedetector.ConfigInternalForTests.CONFIG_AUTO_ENABLED_GEO_DISABLED;
+import static com.android.server.timezonedetector.ConfigInternalForTests.CONFIG_AUTO_ENABLED_GEO_ENABLED;
+import static com.android.server.timezonedetector.ConfigInternalForTests.CONFIG_USER_RESTRICTED_AUTO_ENABLED;
+import static com.android.server.timezonedetector.ConfigInternalForTests.USER_ID;
 import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_HIGH;
 import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_HIGHEST;
 import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.TELEPHONY_SCORE_LOW;
@@ -68,6 +74,7 @@
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion.MatchType;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion.Quality;
 import android.service.timezone.TimeZoneProviderStatus;
+import android.util.IndentingPrintWriter;
 
 import com.android.server.SystemTimeZone.TimeZoneConfidence;
 import com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.QualifiedTelephonyTimeZoneSuggestion;
@@ -82,6 +89,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.function.Function;
@@ -92,7 +100,6 @@
 @RunWith(JUnitParamsRunner.class)
 public class TimeZoneDetectorStrategyImplTest {
 
-    private static final @UserIdInt int USER_ID = 9876;
     private static final long ARBITRARY_ELAPSED_REALTIME_MILLIS = 1234;
     /** A time zone used for initialization that does not occur elsewhere in tests. */
     private static final String ARBITRARY_TIME_ZONE_ID = "Etc/UTC";
@@ -101,7 +108,7 @@
 
     // Telephony test cases are ordered so that each successive one is of the same or higher score
     // than the previous.
-    private static final TelephonyTestCase[] TELEPHONY_TEST_CASES = new TelephonyTestCase[] {
+    private static final TelephonyTestCase[] TELEPHONY_TEST_CASES = new TelephonyTestCase[]{
             newTelephonyTestCase(MATCH_TYPE_NETWORK_COUNTRY_ONLY,
                     QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS, TELEPHONY_SCORE_LOW),
             newTelephonyTestCase(MATCH_TYPE_NETWORK_COUNTRY_ONLY,
@@ -118,90 +125,6 @@
                     TELEPHONY_SCORE_HIGHEST),
     };
 
-    private static final ConfigurationInternal CONFIG_USER_RESTRICTED_AUTO_DISABLED =
-            new ConfigurationInternal.Builder()
-                    .setUserId(USER_ID)
-                    .setTelephonyDetectionFeatureSupported(true)
-                    .setGeoDetectionFeatureSupported(true)
-                    .setTelephonyFallbackSupported(false)
-                    .setGeoDetectionRunInBackgroundEnabled(false)
-                    .setEnhancedMetricsCollectionEnabled(false)
-                    .setUserConfigAllowed(false)
-                    .setAutoDetectionEnabledSetting(false)
-                    .setLocationEnabledSetting(true)
-                    .setGeoDetectionEnabledSetting(false)
-                    .build();
-
-    private static final ConfigurationInternal CONFIG_USER_RESTRICTED_AUTO_ENABLED =
-            new ConfigurationInternal.Builder()
-                    .setUserId(USER_ID)
-                    .setTelephonyDetectionFeatureSupported(true)
-                    .setGeoDetectionFeatureSupported(true)
-                    .setTelephonyFallbackSupported(false)
-                    .setGeoDetectionRunInBackgroundEnabled(false)
-                    .setEnhancedMetricsCollectionEnabled(false)
-                    .setUserConfigAllowed(false)
-                    .setAutoDetectionEnabledSetting(true)
-                    .setLocationEnabledSetting(true)
-                    .setGeoDetectionEnabledSetting(true)
-                    .build();
-
-    private static final ConfigurationInternal CONFIG_AUTO_DETECT_NOT_SUPPORTED =
-            new ConfigurationInternal.Builder()
-                    .setUserId(USER_ID)
-                    .setTelephonyDetectionFeatureSupported(false)
-                    .setGeoDetectionFeatureSupported(false)
-                    .setTelephonyFallbackSupported(false)
-                    .setGeoDetectionRunInBackgroundEnabled(false)
-                    .setEnhancedMetricsCollectionEnabled(false)
-                    .setUserConfigAllowed(true)
-                    .setAutoDetectionEnabledSetting(false)
-                    .setLocationEnabledSetting(true)
-                    .setGeoDetectionEnabledSetting(false)
-                    .build();
-
-    private static final ConfigurationInternal CONFIG_AUTO_DISABLED_GEO_DISABLED =
-            new ConfigurationInternal.Builder()
-                    .setUserId(USER_ID)
-                    .setTelephonyDetectionFeatureSupported(true)
-                    .setGeoDetectionFeatureSupported(true)
-                    .setTelephonyFallbackSupported(false)
-                    .setGeoDetectionRunInBackgroundEnabled(false)
-                    .setEnhancedMetricsCollectionEnabled(false)
-                    .setUserConfigAllowed(true)
-                    .setAutoDetectionEnabledSetting(false)
-                    .setLocationEnabledSetting(true)
-                    .setGeoDetectionEnabledSetting(false)
-                    .build();
-
-    private static final ConfigurationInternal CONFIG_AUTO_ENABLED_GEO_DISABLED =
-            new ConfigurationInternal.Builder()
-                    .setUserId(USER_ID)
-                    .setTelephonyDetectionFeatureSupported(true)
-                    .setGeoDetectionFeatureSupported(true)
-                    .setTelephonyFallbackSupported(false)
-                    .setGeoDetectionRunInBackgroundEnabled(false)
-                    .setEnhancedMetricsCollectionEnabled(false)
-                    .setUserConfigAllowed(true)
-                    .setAutoDetectionEnabledSetting(true)
-                    .setLocationEnabledSetting(true)
-                    .setGeoDetectionEnabledSetting(false)
-                    .build();
-
-    private static final ConfigurationInternal CONFIG_AUTO_ENABLED_GEO_ENABLED =
-            new ConfigurationInternal.Builder()
-                    .setUserId(USER_ID)
-                    .setTelephonyDetectionFeatureSupported(true)
-                    .setGeoDetectionFeatureSupported(true)
-                    .setTelephonyFallbackSupported(false)
-                    .setGeoDetectionRunInBackgroundEnabled(false)
-                    .setEnhancedMetricsCollectionEnabled(false)
-                    .setUserConfigAllowed(true)
-                    .setAutoDetectionEnabledSetting(true)
-                    .setLocationEnabledSetting(true)
-                    .setGeoDetectionEnabledSetting(true)
-                    .build();
-
     private static final TelephonyTimeZoneAlgorithmStatus TELEPHONY_ALGORITHM_RUNNING_STATUS =
             new TelephonyTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING);
 
@@ -421,7 +344,7 @@
                 new QualifiedTelephonyTimeZoneSuggestion(slotIndex1TimeZoneSuggestion,
                         TELEPHONY_SCORE_NONE);
         script.verifyLatestQualifiedTelephonySuggestionReceived(
-                SLOT_INDEX1, expectedSlotIndex1ScoredSuggestion)
+                        SLOT_INDEX1, expectedSlotIndex1ScoredSuggestion)
                 .verifyLatestQualifiedTelephonySuggestionReceived(SLOT_INDEX2, null);
         assertEquals(expectedSlotIndex1ScoredSuggestion,
                 mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
@@ -629,7 +552,7 @@
      */
     @Test
     public void testTelephonySuggestionMultipleSlotIndexSuggestionScoringAndSlotIndexBias() {
-        String[] zoneIds = { "Europe/London", "Europe/Paris" };
+        String[] zoneIds = {"Europe/London", "Europe/Paris"};
         TelephonyTimeZoneSuggestion emptySlotIndex1Suggestion = createEmptySlotIndex1Suggestion();
         TelephonyTimeZoneSuggestion emptySlotIndex2Suggestion = createEmptySlotIndex2Suggestion();
         QualifiedTelephonyTimeZoneSuggestion expectedEmptySlotIndex1ScoredSuggestion =
@@ -672,7 +595,7 @@
 
             // Assert internal service state.
             script.verifyLatestQualifiedTelephonySuggestionReceived(
-                    SLOT_INDEX1, expectedZoneSlotIndex1ScoredSuggestion)
+                            SLOT_INDEX1, expectedZoneSlotIndex1ScoredSuggestion)
                     .verifyLatestQualifiedTelephonySuggestionReceived(
                             SLOT_INDEX2, expectedEmptySlotIndex2ScoredSuggestion);
             assertEquals(expectedZoneSlotIndex1ScoredSuggestion,
@@ -805,14 +728,14 @@
         boolean bypassUserPolicyChecks = false;
         boolean expectedResult = true;
         script.simulateManualTimeZoneSuggestion(
-                USER_ID, manualSuggestion, bypassUserPolicyChecks, expectedResult)
+                        USER_ID, manualSuggestion, bypassUserPolicyChecks, expectedResult)
                 .verifyTimeZoneChangedAndReset(manualSuggestion);
 
         assertEquals(manualSuggestion, mTimeZoneDetectorStrategy.getLatestManualSuggestion());
     }
 
     @Test
-    @Parameters({ "true,true", "true,false", "false,true", "false,false" })
+    @Parameters({"true,true", "true,false", "false,true", "false,false"})
     public void testManualSuggestion_autoTimeEnabled_userRestrictions(
             boolean userConfigAllowed, boolean bypassUserPolicyChecks) {
         ConfigurationInternal config =
@@ -834,7 +757,7 @@
     }
 
     @Test
-    @Parameters({ "true,true", "true,false", "false,true", "false,false" })
+    @Parameters({"true,true", "true,false", "false,true", "false,false"})
     public void testManualSuggestion_autoTimeDisabled_userRestrictions(
             boolean userConfigAllowed, boolean bypassUserPolicyChecks) {
         ConfigurationInternal config =
@@ -849,7 +772,7 @@
         ManualTimeZoneSuggestion manualSuggestion = createManualSuggestion("Europe/Paris");
         boolean expectedResult = userConfigAllowed || bypassUserPolicyChecks;
         script.simulateManualTimeZoneSuggestion(
-                        USER_ID, manualSuggestion, bypassUserPolicyChecks, expectedResult);
+                USER_ID, manualSuggestion, bypassUserPolicyChecks, expectedResult);
         if (expectedResult) {
             script.verifyTimeZoneChangedAndReset(manualSuggestion);
             assertEquals(manualSuggestion, mTimeZoneDetectorStrategy.getLatestManualSuggestion());
@@ -1258,7 +1181,6 @@
             script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
                     .verifyTimeZoneChangedAndReset(locationAlgorithmEvent)
                     .verifyTelephonyFallbackIsEnabled(false);
-
         }
 
         // Demonstrate what happens when geolocation is uncertain when telephony fallback is
@@ -1569,7 +1491,7 @@
         boolean bypassUserPolicyChecks = false;
         boolean expectedResult = true;
         script.simulateManualTimeZoneSuggestion(
-                USER_ID, manualSuggestion, bypassUserPolicyChecks, expectedResult)
+                        USER_ID, manualSuggestion, bypassUserPolicyChecks, expectedResult)
                 .verifyTimeZoneChangedAndReset(manualSuggestion);
         expectedDeviceTimeZoneId = manualSuggestion.getZoneId();
         assertMetricsState(expectedInternalConfig, expectedDeviceTimeZoneId,
@@ -1880,6 +1802,7 @@
             boolean actualResult = mTimeZoneDetectorStrategy.suggestManualTimeZone(
                     userId, manualTimeZoneSuggestion, bypassUserPolicyChecks);
             assertEquals(expectedResult, actualResult);
+
             return this;
         }
 
@@ -2001,4 +1924,34 @@
         return new TelephonyTestCase(matchType, quality, expectedScore);
     }
 
+    static class FakeTimeZoneChangeEventListener implements TimeZoneChangeListener {
+        private final List<TimeZoneChangeEvent> mEvents = new ArrayList<>();
+
+        FakeTimeZoneChangeEventListener() {
+        }
+
+        @Override
+        public void process(TimeZoneChangeEvent event) {
+            mEvents.add(event);
+        }
+
+        public List<TimeZoneChangeEvent> getTimeZoneChangeEvents() {
+            return mEvents;
+        }
+
+        @Override
+        public void dump(IndentingPrintWriter ipw) {
+            // No-op for tests
+        }
+    }
+
+    private static void assertEmpty(Collection<?> collection) {
+        assertTrue(
+                "Expected empty, but contains (" + collection.size() + ") elements: " + collection,
+                collection.isEmpty());
+    }
+
+    private static void assertNotEmpty(Collection<?> collection) {
+        assertFalse("Expected not empty: " + collection, collection.isEmpty());
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 8e79514..f41805d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -66,7 +66,6 @@
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED;
-import static com.android.server.notification.Flags.FLAG_NOTIFICATION_VERIFY_CHANNEL_SOUND_URI;
 import static com.android.server.notification.Flags.FLAG_PERSIST_INCOMPLETE_RESTORE_DATA;
 import static com.android.server.notification.NotificationChannelLogger.NotificationChannelEvent.NOTIFICATION_CHANNEL_UPDATED_BY_USER;
 import static com.android.server.notification.PreferencesHelper.DEFAULT_BUBBLE_PREFERENCE;
@@ -164,6 +163,7 @@
 import com.android.os.AtomsProto.PackageNotificationPreferences;
 import com.android.server.UiServiceTestCase;
 import com.android.server.notification.PermissionHelper.PackagePermission;
+import com.android.server.uri.UriGrantsManagerInternal;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -179,6 +179,9 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
 import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
 import java.io.ByteArrayInputStream;
@@ -199,9 +202,6 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ThreadLocalRandom;
 
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
-
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4.class)
 @EnableFlags(FLAG_PERSIST_INCOMPLETE_RESTORE_DATA)
@@ -239,9 +239,10 @@
 
     private NotificationManager.Policy mTestNotificationPolicy;
 
-    private PreferencesHelper mHelper;
-    // fresh object for testing xml reading
-    private PreferencesHelper mXmlHelper;
+    private TestPreferencesHelper mHelper;
+    // fresh object for testing xml reading; also TestPreferenceHelper in order to avoid interacting
+    // with real IpcDataCaches
+    private TestPreferencesHelper mXmlHelper;
     private AudioAttributes mAudioAttributes;
     private NotificationChannelLoggerFake mLogger = new NotificationChannelLoggerFake();
 
@@ -378,10 +379,10 @@
         when(mUserProfiles.getCurrentProfileIds()).thenReturn(currentProfileIds);
         when(mClock.millis()).thenReturn(System.currentTimeMillis());
 
-        mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+        mHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
                 mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
                 mUgmInternal, false, mClock);
-        mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+        mXmlHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
                 mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
                 mUgmInternal, false, mClock);
         resetZenModeHelper();
@@ -793,7 +794,7 @@
 
     @Test
     public void testReadXml_oldXml_migrates() throws Exception {
-        mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+        mXmlHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
                 mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
                 mUgmInternal, /* showReviewPermissionsNotification= */ true, mClock);
 
@@ -929,7 +930,7 @@
 
     @Test
     public void testReadXml_newXml_noMigration_showPermissionNotification() throws Exception {
-        mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+        mXmlHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
                 mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
                 mUgmInternal, /* showReviewPermissionsNotification= */ true, mClock);
 
@@ -988,7 +989,7 @@
 
     @Test
     public void testReadXml_newXml_permissionNotificationOff() throws Exception {
-        mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+        mHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
                 mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
                 mUgmInternal, /* showReviewPermissionsNotification= */ false, mClock);
 
@@ -1047,7 +1048,7 @@
 
     @Test
     public void testReadXml_newXml_noMigration_noPermissionNotification() throws Exception {
-        mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+        mHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
                 mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
                 mUgmInternal, /* showReviewPermissionsNotification= */ true, mClock);
 
@@ -1641,7 +1642,7 @@
         serializer.flush();
 
         // simulate load after reboot
-        mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+        mXmlHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
                 mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
                 mUgmInternal, false, mClock);
         loadByteArrayXml(baos.toByteArray(), false, USER_ALL);
@@ -1696,7 +1697,7 @@
                 Duration.ofDays(2).toMillis() + System.currentTimeMillis());
 
         // simulate load after reboot
-        mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+        mXmlHelper = new TestPreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
                 mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
                 mUgmInternal, false, mClock);
         loadByteArrayXml(xml.getBytes(), false, USER_ALL);
@@ -1774,10 +1775,10 @@
         when(contentResolver.getResourceId(ANDROID_RES_SOUND_URI)).thenReturn(resId).thenThrow(
                 new FileNotFoundException("")).thenReturn(resId);
 
-        mHelper = new PreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper,
+        mHelper = new TestPreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper,
                 mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
                 mUgmInternal, false, mClock);
-        mXmlHelper = new PreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper,
+        mXmlHelper = new TestPreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper,
                 mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
                 mUgmInternal, false, mClock);
 
@@ -3190,7 +3191,6 @@
     }
 
     @Test
-    @EnableFlags(FLAG_NOTIFICATION_VERIFY_CHANNEL_SOUND_URI)
     public void testCreateChannel_noSoundUriPermission_contentSchemeVerified() {
         final Uri sound = Uri.parse(SCHEME_CONTENT + "://media/test/sound/uri");
 
@@ -3210,7 +3210,6 @@
     }
 
     @Test
-    @EnableFlags(FLAG_NOTIFICATION_VERIFY_CHANNEL_SOUND_URI)
     public void testCreateChannel_noSoundUriPermission_fileSchemaIgnored() {
         final Uri sound = Uri.parse(SCHEME_FILE + "://path/sound");
 
@@ -3229,7 +3228,6 @@
     }
 
     @Test
-    @EnableFlags(FLAG_NOTIFICATION_VERIFY_CHANNEL_SOUND_URI)
     public void testCreateChannel_noSoundUriPermission_resourceSchemaIgnored() {
         final Uri sound = Uri.parse(SCHEME_ANDROID_RESOURCE + "://resId/sound");
 
@@ -6573,4 +6571,223 @@
         mHelper.setCanBePromoted(PKG_P, UID_P, false, false);
         assertThat(mHelper.canBePromoted(PKG_P, UID_P)).isTrue();
     }
+
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+    public void testInvalidateChannelCache_invalidateOnCreationAndChange() {
+        mHelper.resetCacheInvalidation();
+        NotificationChannel channel = new NotificationChannel("id", "name", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false, UID_N_MR1,
+                false);
+
+        // new channel should invalidate the cache.
+        assertThat(mHelper.hasCacheBeenInvalidated()).isTrue();
+
+        // when the channel data is updated, should invalidate the cache again after that.
+        mHelper.resetCacheInvalidation();
+        NotificationChannel newChannel = channel.copy();
+        newChannel.setName("new name");
+        newChannel.setImportance(IMPORTANCE_HIGH);
+        mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, newChannel, true, UID_N_MR1, false);
+        assertThat(mHelper.hasCacheBeenInvalidated()).isTrue();
+
+        // also for conversations
+        mHelper.resetCacheInvalidation();
+        String parentId = "id";
+        String convId = "conversation";
+        NotificationChannel conv = new NotificationChannel(
+                String.format(CONVERSATION_CHANNEL_ID_FORMAT, parentId, convId), "conversation",
+                IMPORTANCE_DEFAULT);
+        conv.setConversationId(parentId, convId);
+        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, conv, true, false, UID_N_MR1,
+                false);
+        assertThat(mHelper.hasCacheBeenInvalidated()).isTrue();
+
+        mHelper.resetCacheInvalidation();
+        NotificationChannel newConv = conv.copy();
+        newConv.setName("changed");
+        mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, newConv, true, UID_N_MR1, false);
+        assertThat(mHelper.hasCacheBeenInvalidated()).isTrue();
+    }
+
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+    public void testInvalidateChannelCache_invalidateOnDelete() {
+        NotificationChannel channel = new NotificationChannel("id", "name", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false, UID_N_MR1,
+                false);
+
+        // ignore any invalidations up until now
+        mHelper.resetCacheInvalidation();
+
+        mHelper.deleteNotificationChannel(PKG_N_MR1, UID_N_MR1, "id", UID_N_MR1, false);
+        assertThat(mHelper.hasCacheBeenInvalidated()).isTrue();
+
+        // recreate channel and now permanently delete
+        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false, UID_N_MR1,
+                false);
+        mHelper.resetCacheInvalidation();
+        mHelper.permanentlyDeleteNotificationChannel(PKG_N_MR1, UID_N_MR1, "id");
+        assertThat(mHelper.hasCacheBeenInvalidated()).isTrue();
+    }
+
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+    public void testInvalidateChannelCache_noInvalidationWhenNoChange() {
+        NotificationChannel channel = new NotificationChannel("id", "name", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false, UID_N_MR1,
+                false);
+
+        // ignore any invalidations up until now
+        mHelper.resetCacheInvalidation();
+
+        // newChannel, same as the old channel
+        NotificationChannel newChannel = channel.copy();
+        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, newChannel, true, false, UID_N_MR1,
+                false);
+        mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, newChannel, true, UID_N_MR1, false);
+
+        // because there were no effective changes, we should not see any cache invalidations
+        assertThat(mHelper.hasCacheBeenInvalidated()).isFalse();
+
+        // deletions of a nonexistent channel also don't change anything
+        mHelper.resetCacheInvalidation();
+        mHelper.deleteNotificationChannel(PKG_N_MR1, UID_N_MR1, "nonexistent", UID_N_MR1, false);
+        assertThat(mHelper.hasCacheBeenInvalidated()).isFalse();
+    }
+
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+    public void testInvalidateCache_multipleUsersAndPackages() {
+        // Setup: create channels for:
+        // pkg O, user
+        // pkg O, work (same channel ID, different user)
+        // pkg N_MR1, user
+        // pkg N_MR1, user, conversation child of above
+        String p2u1ConvId = String.format(CONVERSATION_CHANNEL_ID_FORMAT, "p2", "conv");
+        NotificationChannel p1u1 = new NotificationChannel("p1", "p1u1", IMPORTANCE_DEFAULT);
+        NotificationChannel p1u2 = new NotificationChannel("p1", "p1u2", IMPORTANCE_DEFAULT);
+        NotificationChannel p2u1 = new NotificationChannel("p2", "p2u1", IMPORTANCE_DEFAULT);
+        NotificationChannel p2u1Conv = new NotificationChannel(p2u1ConvId, "p2u1 conv",
+                IMPORTANCE_DEFAULT);
+        p2u1Conv.setConversationId("p2", "conv");
+
+        mHelper.createNotificationChannel(PKG_O, UID_O, p1u1, true,
+                false, UID_O, false);
+        mHelper.createNotificationChannel(PKG_O, UID_O + UserHandle.PER_USER_RANGE, p1u2, true,
+                false, UID_O + UserHandle.PER_USER_RANGE, false);
+        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, p2u1, true,
+                false, UID_N_MR1, false);
+        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, p2u1Conv, true,
+                false, UID_N_MR1, false);
+        mHelper.resetCacheInvalidation();
+
+        // Update to an existent channel, with a change: should invalidate
+        NotificationChannel p1u1New = p1u1.copy();
+        p1u1New.setName("p1u1 new");
+        mHelper.updateNotificationChannel(PKG_O, UID_O, p1u1New, true, UID_O, false);
+        assertThat(mHelper.hasCacheBeenInvalidated()).isTrue();
+
+        // Do it again, but no change for this user
+        mHelper.resetCacheInvalidation();
+        mHelper.updateNotificationChannel(PKG_O, UID_O, p1u1New.copy(), true, UID_O, false);
+        assertThat(mHelper.hasCacheBeenInvalidated()).isFalse();
+
+        // Delete conversations, but for a package without those conversations
+        mHelper.resetCacheInvalidation();
+        mHelper.deleteConversations(PKG_O, UID_O, Set.of(p2u1Conv.getConversationId()), UID_O,
+                false);
+        assertThat(mHelper.hasCacheBeenInvalidated()).isFalse();
+
+        // Now delete conversations for the right package
+        mHelper.resetCacheInvalidation();
+        mHelper.deleteConversations(PKG_N_MR1, UID_N_MR1, Set.of(p2u1Conv.getConversationId()),
+                UID_N_MR1, false);
+        assertThat(mHelper.hasCacheBeenInvalidated()).isTrue();
+    }
+
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+    public void testInvalidateCache_userRemoved() throws Exception {
+        NotificationChannel c1 = new NotificationChannel("id1", "name1", IMPORTANCE_DEFAULT);
+        int uid1 = UserHandle.getUid(1, 1);
+        setUpPackageWithUid("pkg1", uid1);
+        mHelper.createNotificationChannel("pkg1", uid1, c1, true, false, uid1, false);
+        mHelper.resetCacheInvalidation();
+
+        // delete user 1; should invalidate cache
+        mHelper.onUserRemoved(1);
+        assertThat(mHelper.hasCacheBeenInvalidated()).isTrue();
+    }
+
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+    public void testInvalidateCache_packagesChanged() {
+        NotificationChannel channel1 =
+                new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false,
+                UID_N_MR1, false);
+
+        // package deleted: expect cache invalidation
+        mHelper.resetCacheInvalidation();
+        mHelper.onPackagesChanged(true, USER_SYSTEM, new String[]{PKG_N_MR1},
+                new int[]{UID_N_MR1});
+        assertThat(mHelper.hasCacheBeenInvalidated()).isTrue();
+
+        // re-created: expect cache invalidation again
+        mHelper.resetCacheInvalidation();
+        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false,
+                UID_N_MR1, false);
+        mHelper.onPackagesChanged(false, USER_SYSTEM, new String[]{PKG_N_MR1},
+                new int[]{UID_N_MR1});
+        assertThat(mHelper.hasCacheBeenInvalidated()).isTrue();
+    }
+
+    @Test
+    @DisableFlags(android.app.Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+    public void testInvalidateCache_flagOff_neverTouchesCache() {
+        // Do a bunch of channel-changing operations.
+        NotificationChannel channel =
+                new NotificationChannel("id", "name1", NotificationManager.IMPORTANCE_HIGH);
+        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false,
+                UID_N_MR1, false);
+
+        NotificationChannel copy = channel.copy();
+        copy.setName("name2");
+        mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, copy, true, UID_N_MR1, false);
+        mHelper.deleteNotificationChannel(PKG_N_MR1, UID_N_MR1, "id", UID_N_MR1, false);
+
+        assertThat(mHelper.hasCacheBeenInvalidated()).isFalse();
+    }
+
+    // Test version of PreferencesHelper whose only functional difference is that it does not
+    // interact with the real IpcDataCache, and instead tracks whether or not the cache has been
+    // invalidated since creation or the last reset.
+    private static class TestPreferencesHelper extends PreferencesHelper {
+        private boolean mCacheInvalidated = false;
+
+        TestPreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
+                ZenModeHelper zenHelper, PermissionHelper permHelper, PermissionManager permManager,
+                NotificationChannelLogger notificationChannelLogger,
+                AppOpsManager appOpsManager, ManagedServices.UserProfiles userProfiles,
+                UriGrantsManagerInternal ugmInternal,
+                boolean showReviewPermissionsNotification, Clock clock) {
+            super(context, pm, rankingHandler, zenHelper, permHelper, permManager,
+                    notificationChannelLogger, appOpsManager, userProfiles, ugmInternal,
+                    showReviewPermissionsNotification, clock);
+        }
+
+        @Override
+        protected void invalidateNotificationChannelCache() {
+            mCacheInvalidated = true;
+        }
+
+        boolean hasCacheBeenInvalidated() {
+            return mCacheInvalidated;
+        }
+
+        void resetCacheInvalidation() {
+            mCacheInvalidated = false;
+        }
+    }
 }
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/BasicToPwleSegmentAdapterTest.java b/services/tests/vibrator/src/com/android/server/vibrator/BasicToPwleSegmentAdapterTest.java
new file mode 100644
index 0000000..09f573c
--- /dev/null
+++ b/services/tests/vibrator/src/com/android/server/vibrator/BasicToPwleSegmentAdapterTest.java
@@ -0,0 +1,158 @@
+/*
+ * 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 com.google.common.truth.Truth.assertThat;
+
+import android.hardware.vibrator.IVibrator;
+import android.os.VibratorInfo;
+import android.os.vibrator.BasicPwleSegment;
+import android.os.vibrator.Flags;
+import android.os.vibrator.PwleSegment;
+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;
+import java.util.stream.IntStream;
+
+public class BasicToPwleSegmentAdapterTest {
+
+    private static final float TEST_RESONANT_FREQUENCY = 150;
+    private static final float[] TEST_FREQUENCIES =
+            new float[]{90f, 120f, 150f, 60f, 30f, 210f, 270f, 300f, 240f, 180f};
+    private static final float[] TEST_OUTPUT_ACCELERATIONS =
+            new float[]{1.2f, 1.8f, 2.4f, 0.6f, 0.1f, 2.2f, 1.0f, 0.5f, 1.9f, 3.0f};
+
+    private static final VibratorInfo.FrequencyProfile TEST_FREQUENCY_PROFILE =
+            new VibratorInfo.FrequencyProfile(TEST_RESONANT_FREQUENCY, TEST_FREQUENCIES,
+                    TEST_OUTPUT_ACCELERATIONS);
+
+    private static final VibratorInfo.FrequencyProfile EMPTY_FREQUENCY_PROFILE =
+            new VibratorInfo.FrequencyProfile(TEST_RESONANT_FREQUENCY, null, null);
+
+    private BasicToPwleSegmentAdapter mAdapter;
+
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+    @Before
+    public void setUp() throws Exception {
+        mAdapter = new BasicToPwleSegmentAdapter();
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    public void testBasicPwleSegments_withFeatureFlagDisabled_returnsOriginalSegments() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                //  startIntensity, endIntensity, startSharpness, endSharpness, duration
+                new BasicPwleSegment(0.2f, 0.8f, 0.2f, 0.4f, 20),
+                new BasicPwleSegment(0.8f, 0.2f, 0.4f, 0.5f, 100),
+                new BasicPwleSegment(0.2f, 0.65f, 0.5f, 0.5f, 50)));
+        List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
+
+        VibratorInfo vibratorInfo = createVibratorInfo(
+                TEST_FREQUENCY_PROFILE, IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2);
+
+        assertThat(mAdapter.adaptToVibrator(vibratorInfo, segments, /*repeatIndex= */ -1))
+                .isEqualTo(-1);
+        assertThat(mAdapter.adaptToVibrator(vibratorInfo, segments, /*repeatIndex= */ 1))
+                .isEqualTo(1);
+
+        assertThat(segments).isEqualTo(originalSegments);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    public void testBasicPwleSegments_noPwleCapability_returnsOriginalSegments() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                //  startIntensity, endIntensity, startSharpness, endSharpness, duration
+                new BasicPwleSegment(0.2f, 0.8f, 0.2f, 0.4f, 20),
+                new BasicPwleSegment(0.8f, 0.2f, 0.4f, 0.5f, 100),
+                new BasicPwleSegment(0.2f, 0.65f, 0.5f, 0.5f, 50)));
+        List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
+
+        VibratorInfo vibratorInfo = createVibratorInfo(TEST_FREQUENCY_PROFILE);
+
+        assertThat(mAdapter.adaptToVibrator(vibratorInfo, segments, /*repeatIndex= */ -1))
+                .isEqualTo(-1);
+        assertThat(mAdapter.adaptToVibrator(vibratorInfo, segments, /*repeatIndex= */ 1))
+                .isEqualTo(1);
+
+        assertThat(segments).isEqualTo(originalSegments);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    public void testBasicPwleSegments_invalidFrequencyProfile_returnsOriginalSegments() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                //  startIntensity, endIntensity, startSharpness, endSharpness, duration
+                new BasicPwleSegment(0.2f, 0.8f, 0.2f, 0.4f, 20),
+                new BasicPwleSegment(0.8f, 0.2f, 0.4f, 0.5f, 100),
+                new BasicPwleSegment(0.2f, 0.65f, 0.5f, 0.5f, 50)));
+        List<VibrationEffectSegment> originalSegments = new ArrayList<>(segments);
+        VibratorInfo vibratorInfo = createVibratorInfo(
+                EMPTY_FREQUENCY_PROFILE, IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2);
+
+        assertThat(mAdapter.adaptToVibrator(vibratorInfo, segments, /*repeatIndex= */ -1))
+                .isEqualTo(-1);
+        assertThat(mAdapter.adaptToVibrator(vibratorInfo, segments, /*repeatIndex= */ 1))
+                .isEqualTo(1);
+
+        assertThat(segments).isEqualTo(originalSegments);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    public void testBasicPwleSegments_withPwleCapability_adaptSegmentsCorrectly() {
+        List<VibrationEffectSegment> segments = new ArrayList<>(Arrays.asList(
+                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 40f, /* duration= */ 100),
+                //  startIntensity, endIntensity, startSharpness, endSharpness, duration
+                new BasicPwleSegment(0.0f, 1.0f, 0.0f, 1.0f, 100),
+                new BasicPwleSegment(0.0f, 1.0f, 0.0f, 1.0f, 100),
+                new BasicPwleSegment(0.0f, 1.0f, 0.0f, 1.0f, 100)));
+        List<VibrationEffectSegment> expectedSegments = Arrays.asList(
+                new StepSegment(/* amplitude= */ 1, /* frequencyHz= */ 40f, /* duration= */ 100),
+                //  startAmplitude, endAmplitude, startFrequencyHz, endFrequencyHz, duration
+                new PwleSegment(0.0f, 1.0f, 30.0f, 300.0f, 100),
+                new PwleSegment(0.0f, 1.0f, 30.0f, 300.0f, 100),
+                new PwleSegment(0.0f, 1.0f, 30.0f, 300.0f, 100));
+        VibratorInfo vibratorInfo = createVibratorInfo(
+                TEST_FREQUENCY_PROFILE, IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2);
+
+        assertThat(mAdapter.adaptToVibrator(vibratorInfo, segments, /*repeatIndex= */ 1))
+                .isEqualTo(1);
+
+        assertThat(segments).isEqualTo(expectedSegments);
+    }
+
+    private static VibratorInfo createVibratorInfo(VibratorInfo.FrequencyProfile frequencyProfile,
+            int... capabilities) {
+        return new VibratorInfo.Builder(0)
+                .setCapabilities(IntStream.of(capabilities).reduce((a, b) -> a | b).orElse(0))
+                .setFrequencyProfile(frequencyProfile)
+                .build();
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java
index 9d191ce..a0727a7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java
@@ -335,7 +335,7 @@
         }
 
         private AppCompatOrientationOverrides getTopOrientationOverrides() {
-            return activity().top().mAppCompatController.getAppCompatOrientationOverrides();
+            return activity().top().mAppCompatController.getOrientationOverrides();
         }
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
index a21ab5d..4faa714 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
@@ -601,7 +601,7 @@
         }
 
         private AppCompatOrientationOverrides getTopOrientationOverrides() {
-            return activity().top().mAppCompatController.getAppCompatOrientationOverrides();
+            return activity().top().mAppCompatController.getOrientationOverrides();
         }
 
         private AppCompatOrientationPolicy getTopAppCompatOrientationPolicy() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityOverridesTest.java
index 463254c..50419d4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityOverridesTest.java
@@ -159,8 +159,8 @@
         @Override
         void onPostActivityCreation(@NonNull ActivityRecord activity) {
             super.onPostActivityCreation(activity);
-            spyOn(activity.mAppCompatController.getAppCompatReachabilityOverrides());
-            activity.mAppCompatController.getAppCompatReachabilityPolicy()
+            spyOn(activity.mAppCompatController.getReachabilityOverrides());
+            activity.mAppCompatController.getReachabilityPolicy()
                     .setLetterboxInnerBoundsSupplier(mLetterboxInnerBoundsSupplier);
         }
 
@@ -196,7 +196,7 @@
 
         @NonNull
         private AppCompatReachabilityOverrides getAppCompatReachabilityOverrides() {
-            return activity().top().mAppCompatController.getAppCompatReachabilityOverrides();
+            return activity().top().mAppCompatController.getReachabilityOverrides();
         }
 
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityPolicyTest.java
index ddc4de9..09b8bce 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatReachabilityPolicyTest.java
@@ -246,8 +246,8 @@
         @Override
         void onPostActivityCreation(@NonNull ActivityRecord activity) {
             super.onPostActivityCreation(activity);
-            spyOn(activity.mAppCompatController.getAppCompatReachabilityOverrides());
-            activity.mAppCompatController.getAppCompatReachabilityPolicy()
+            spyOn(activity.mAppCompatController.getReachabilityOverrides());
+            activity.mAppCompatController.getReachabilityPolicy()
                     .setLetterboxInnerBoundsSupplier(mLetterboxInnerBoundsSupplier);
         }
 
@@ -281,12 +281,12 @@
 
         @NonNull
         private AppCompatReachabilityOverrides getAppCompatReachabilityOverrides() {
-            return activity().top().mAppCompatController.getAppCompatReachabilityOverrides();
+            return activity().top().mAppCompatController.getReachabilityOverrides();
         }
 
         @NonNull
         private AppCompatReachabilityPolicy getAppCompatReachabilityPolicy() {
-            return activity().top().mAppCompatController.getAppCompatReachabilityPolicy();
+            return activity().top().mAppCompatController.getReachabilityPolicy();
         }
 
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 9d9f24c..96b11a8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -330,7 +330,7 @@
         if (horizontalReachability) {
             final Consumer<Integer> doubleClick =
                     (Integer x) -> {
-                        mActivity.mAppCompatController.getAppCompatReachabilityPolicy()
+                        mActivity.mAppCompatController.getReachabilityPolicy()
                                 .handleDoubleTap(x, displayHeight / 2);
                         mActivity.mRootWindowContainer.performSurfacePlacement();
                     };
@@ -360,7 +360,7 @@
         } else {
             final Consumer<Integer> doubleClick =
                     (Integer y) -> {
-                        mActivity.mAppCompatController.getAppCompatReachabilityPolicy()
+                        mActivity.mAppCompatController.getReachabilityPolicy()
                                 .handleDoubleTap(displayWidth / 2, y);
                         mActivity.mRootWindowContainer.performSurfacePlacement();
                     };
@@ -421,7 +421,7 @@
 
         final Consumer<Integer> doubleClick =
                 (Integer y) -> {
-                    activity.mAppCompatController.getAppCompatReachabilityPolicy()
+                    activity.mAppCompatController.getReachabilityPolicy()
                             .handleDoubleTap(dw / 2, y);
                     activity.mRootWindowContainer.performSurfacePlacement();
                 };
@@ -834,7 +834,7 @@
         // Change the fixed orientation.
         mActivity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
         assertTrue(mActivity.isRelaunching());
-        assertTrue(mActivity.mAppCompatController.getAppCompatOrientationOverrides()
+        assertTrue(mActivity.mAppCompatController.getOrientationOverrides()
                 .getIsRelaunchingAfterRequestedOrientationChanged());
 
         assertFitted();
@@ -3427,7 +3427,7 @@
 
         setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ false);
         final AppCompatReachabilityOverrides reachabilityOverrides =
-                mActivity.mAppCompatController.getAppCompatReachabilityOverrides();
+                mActivity.mAppCompatController.getReachabilityOverrides();
         assertFalse(reachabilityOverrides.isVerticalReachabilityEnabled());
         assertFalse(reachabilityOverrides.isHorizontalReachabilityEnabled());
     }
@@ -3451,7 +3451,7 @@
         assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
 
         // Horizontal reachability is disabled because the app is in split screen.
-        assertFalse(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+        assertFalse(mActivity.mAppCompatController.getReachabilityOverrides()
                 .isHorizontalReachabilityEnabled());
     }
 
@@ -3475,7 +3475,7 @@
         assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
 
         // Vertical reachability is disabled because the app is in split screen.
-        assertFalse(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+        assertFalse(mActivity.mAppCompatController.getReachabilityOverrides()
                 .isVerticalReachabilityEnabled());
     }
 
@@ -3498,7 +3498,7 @@
         // Vertical reachability is disabled because the app does not match parent width
         assertNotEquals(mActivity.getScreenResolvedBounds().width(),
                 mActivity.mDisplayContent.getBounds().width());
-        assertFalse(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+        assertFalse(mActivity.mAppCompatController.getReachabilityOverrides()
                 .isVerticalReachabilityEnabled());
     }
 
@@ -3516,7 +3516,7 @@
         assertEquals(new Rect(0, 0, 0, 0), mActivity.getBounds());
 
         // Vertical reachability is still enabled as resolved bounds is not empty
-        assertTrue(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+        assertTrue(mActivity.mAppCompatController.getReachabilityOverrides()
                 .isVerticalReachabilityEnabled());
     }
 
@@ -3533,7 +3533,7 @@
         assertEquals(new Rect(0, 0, 0, 0), mActivity.getBounds());
 
         // Horizontal reachability is still enabled as resolved bounds is not empty
-        assertTrue(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+        assertTrue(mActivity.mAppCompatController.getReachabilityOverrides()
                 .isHorizontalReachabilityEnabled());
     }
 
@@ -3548,7 +3548,7 @@
         prepareMinAspectRatio(mActivity, OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
                 SCREEN_ORIENTATION_PORTRAIT);
 
-        assertTrue(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+        assertTrue(mActivity.mAppCompatController.getReachabilityOverrides()
                 .isHorizontalReachabilityEnabled());
     }
 
@@ -3563,7 +3563,7 @@
         prepareMinAspectRatio(mActivity, OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
                 SCREEN_ORIENTATION_LANDSCAPE);
 
-        assertTrue(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+        assertTrue(mActivity.mAppCompatController.getReachabilityOverrides()
                 .isVerticalReachabilityEnabled());
     }
 
@@ -3585,7 +3585,7 @@
         // Horizontal reachability is disabled because the app does not match parent height
         assertNotEquals(mActivity.getScreenResolvedBounds().height(),
                 mActivity.mDisplayContent.getBounds().height());
-        assertFalse(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+        assertFalse(mActivity.mAppCompatController.getReachabilityOverrides()
                 .isHorizontalReachabilityEnabled());
     }
 
@@ -3608,7 +3608,7 @@
         // Horizontal reachability is enabled because the app matches parent height
         assertEquals(mActivity.getScreenResolvedBounds().height(),
                 mActivity.mDisplayContent.getBounds().height());
-        assertTrue(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+        assertTrue(mActivity.mAppCompatController.getReachabilityOverrides()
                 .isHorizontalReachabilityEnabled());
     }
 
@@ -3631,7 +3631,7 @@
         // Vertical reachability is enabled because the app matches parent width
         assertEquals(mActivity.getScreenResolvedBounds().width(),
                 mActivity.mDisplayContent.getBounds().width());
-        assertTrue(mActivity.mAppCompatController.getAppCompatReachabilityOverrides()
+        assertTrue(mActivity.mAppCompatController.getReachabilityOverrides()
                 .isVerticalReachabilityEnabled());
     }
 
@@ -4315,7 +4315,7 @@
 
         // Make sure app doesn't jump to top (default tabletop position) when unfolding.
         assertEquals(1.0f, mActivity.mAppCompatController
-                .getAppCompatReachabilityOverrides().getVerticalPositionMultiplier(mActivity
+                .getReachabilityOverrides().getVerticalPositionMultiplier(mActivity
                         .getParent().getConfiguration()), 0);
 
         // Simulate display fully open after unfolding.
@@ -4323,7 +4323,7 @@
         doReturn(false).when(mActivity.mDisplayContent).inTransition();
 
         assertEquals(1.0f, mActivity.mAppCompatController
-                .getAppCompatReachabilityOverrides().getVerticalPositionMultiplier(mActivity
+                .getReachabilityOverrides().getVerticalPositionMultiplier(mActivity
                         .getParent().getConfiguration()), 0);
     }
 
@@ -5028,7 +5028,7 @@
 
     private void setUpAllowThinLetterboxed(boolean thinLetterboxAllowed) {
         final AppCompatReachabilityOverrides reachabilityOverrides =
-                mActivity.mAppCompatController.getAppCompatReachabilityOverrides();
+                mActivity.mAppCompatController.getReachabilityOverrides();
         spyOn(reachabilityOverrides);
         doReturn(thinLetterboxAllowed).when(reachabilityOverrides)
                 .allowVerticalReachabilityForThinLetterbox();
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 7082f00..e65e4b0 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -29,6 +29,7 @@
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.annotation.TestApi;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -1886,6 +1887,34 @@
     }
 
     /**
+     * This test API determines the foreground service delegation state for a VoIP app that adds
+     * calls via {@link TelecomManager#addCall(CallAttributes, Executor, OutcomeReceiver,
+     * CallControlCallback, CallEventCallback)}.  Foreground Service Delegation allows applications
+     * to operate in the background  starting in Android 14 and is granted by Telecom via a request
+     * to the ActivityManager.
+     *
+     * @param handle of the voip app that is being checked
+     * @return true if the app has foreground service delegation. Otherwise, false.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_VOIP_CALL_MONITOR_REFACTOR)
+    @TestApi
+    public boolean hasForegroundServiceDelegation(@Nullable PhoneAccountHandle handle) {
+        ITelecomService service = getTelecomService();
+        if (service != null) {
+            try {
+                return service.hasForegroundServiceDelegation(handle, mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                Log.e(TAG,
+                        "RemoteException calling ITelecomService#hasForegroundServiceDelegation.",
+                        e);
+            }
+        }
+        return false;
+    }
+
+    /**
      * Return the line 1 phone number for given phone account.
      *
      * <p>Requires Permission:
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index c85374e..b32379a 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -409,4 +409,10 @@
      */
     void addCall(in CallAttributes callAttributes, in ICallEventCallback callback, String callId,
         String callingPackage);
+
+    /**
+     * @see TelecomServiceImpl#hasForegroundServiceDelegation
+     */
+    boolean hasForegroundServiceDelegation(in PhoneAccountHandle phoneAccountHandle,
+                                                       String callingPackage);
 }
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 63a1281..b7b209b 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -3690,8 +3690,8 @@
      * @param list The list of provisioned satellite subscriber infos.
      * @param executor The executor on which the callback will be called.
      * @param callback The callback object to which the result will be delivered.
-     *                 If the request is successful, {@link OutcomeReceiver#onResult(Object)}
-     *                 will return {@code true}.
+     *                 If the request is successful, {@link OutcomeReceiver#onResult}
+     *                 will be called.
      *                 If the request is not successful,
      *                 {@link OutcomeReceiver#onError(Throwable)} will return an error with
      *                 a SatelliteException.
@@ -3704,7 +3704,7 @@
     @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public void provisionSatellite(@NonNull List<SatelliteSubscriberInfo> list,
             @NonNull @CallbackExecutor Executor executor,
-            @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
+            @NonNull OutcomeReceiver<Void, SatelliteException> callback) {
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
 
@@ -3718,8 +3718,8 @@
                             if (resultData.containsKey(KEY_PROVISION_SATELLITE_TOKENS)) {
                                 boolean isUpdated =
                                         resultData.getBoolean(KEY_PROVISION_SATELLITE_TOKENS);
-                                executor.execute(() -> Binder.withCleanCallingIdentity(() ->
-                                        callback.onResult(isUpdated)));
+                                executor.execute(() -> Binder.withCleanCallingIdentity(
+                                        () -> callback.onResult(null)));
                             } else {
                                 loge("KEY_REQUEST_PROVISION_TOKENS does not exist.");
                                 executor.execute(() -> Binder.withCleanCallingIdentity(() ->
@@ -3751,8 +3751,8 @@
      * @param list The list of deprovisioned satellite subscriber infos.
      * @param executor The executor on which the callback will be called.
      * @param callback The callback object to which the result will be delivered.
-     *                 If the request is successful, {@link OutcomeReceiver#onResult(Object)}
-     *                 will return {@code true}.
+     *                 If the request is successful, {@link OutcomeReceiver#onResult}
+     *                 will be called.
      *                 If the request is not successful,
      *                 {@link OutcomeReceiver#onError(Throwable)} will return an error with
      *                 a SatelliteException.
@@ -3765,7 +3765,7 @@
     @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public void deprovisionSatellite(@NonNull List<SatelliteSubscriberInfo> list,
             @NonNull @CallbackExecutor Executor executor,
-            @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
+            @NonNull OutcomeReceiver<Void, SatelliteException> callback) {
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
 
@@ -3780,7 +3780,7 @@
                                 boolean isUpdated =
                                         resultData.getBoolean(KEY_DEPROVISION_SATELLITE_TOKENS);
                                 executor.execute(() -> Binder.withCleanCallingIdentity(() ->
-                                        callback.onResult(isUpdated)));
+                                        callback.onResult(null)));
                             } else {
                                 loge("KEY_DEPROVISION_SATELLITE_TOKENS does not exist.");
                                 executor.execute(() -> Binder.withCleanCallingIdentity(() ->
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
index 08b5f38..75bd5d1 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
@@ -17,7 +17,6 @@
 package com.android.server.wm.flicker.activityembedding.open
 
 import android.graphics.Rect
-import android.platform.test.annotations.Presubmit
 import android.tools.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.flicker.legacy.FlickerBuilder
 import android.tools.flicker.legacy.LegacyFlickerTest
@@ -68,13 +67,21 @@
         }
     }
 
-    @Ignore("Not applicable to this CUJ.") override fun navBarWindowIsVisibleAtStartAndEnd() {}
+    @Ignore("Not applicable to this CUJ.")
+    @Test
+    override fun navBarWindowIsVisibleAtStartAndEnd() {}
 
-    @FlakyTest(bugId = 291575593) override fun entireScreenCovered() {}
+    @FlakyTest(bugId = 291575593)
+    @Test
+    override fun entireScreenCovered() {}
 
-    @Ignore("Not applicable to this CUJ.") override fun statusBarWindowIsAlwaysVisible() {}
+    @Ignore("Not applicable to this CUJ.")
+    @Test
+    override fun statusBarWindowIsAlwaysVisible() {}
 
-    @Ignore("Not applicable to this CUJ.") override fun statusBarLayerPositionAtStartAndEnd() {}
+    @Ignore("Not applicable to this CUJ.")
+    @Test
+    override fun statusBarLayerPositionAtStartAndEnd() {}
 
     /** Transition begins with a split. */
     @FlakyTest(bugId = 286952194)
@@ -122,7 +129,6 @@
 
     /** Always expand activity is on top of the split. */
     @FlakyTest(bugId = 286952194)
-    @Presubmit
     @Test
     fun endsWithAlwaysExpandActivityOnTop() {
         flicker.assertWmEnd {
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
index 0ca8f37..e413645 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
@@ -176,12 +176,15 @@
     }
 
     @Ignore("Not applicable to this CUJ.")
+    @Test
     override fun visibleLayersShownMoreThanOneConsecutiveEntry() {}
 
     @FlakyTest(bugId = 342596801)
+    @Test
     override fun entireScreenCovered() = super.entireScreenCovered()
 
     @FlakyTest(bugId = 342596801)
+    @Test
     override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
         super.visibleWindowsShownMoreThanOneConsecutiveEntry()
 
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
index b8f11dc..ad083fa 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.server.wm.flicker.ime
 
-import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.Rotation
 import android.tools.flicker.junit.FlickerParametersRunnerFactory
@@ -81,7 +80,6 @@
     }
 
     @FlakyTest(bugId = 290767483)
-    @Postsubmit
     @Test
     fun imeLayerAlphaOneAfterSnapshotStartingWindowRemoval() {
         val layerTrace = flicker.reader.readLayersTrace() ?: error("Unable to read layers trace")