Merge "[1/N] Desks: Add support for adb commands" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index a60ced5..f249884 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -107,7 +107,7 @@
         "com.android.server.flags.services-aconfig-java",
         "com.android.text.flags-aconfig-java",
         "com.android.window.flags.window-aconfig-java",
-        "configinfra_framework_flags_java_lib",
+        "configinfra_framework_flags_java_exported_lib",
         "conscrypt_exported_aconfig_flags_lib",
         "device_policy_aconfig_flags_lib",
         "display_flags_lib",
@@ -467,7 +467,7 @@
         "//apex_available:platform",
         "com.android.art",
         "com.android.art.debug",
-        "com.android.btservices",
+        "com.android.bt",
         "com.android.mediaprovider",
         "com.android.permission",
     ],
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 60ba3b8..829442a 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -96,6 +96,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.UserPackage;
+import android.content.res.Resources;
 import android.net.Uri;
 import android.os.BatteryManager;
 import android.os.BatteryStatsInternal;
@@ -1784,7 +1785,8 @@
         mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
 
         mStartUserBeforeScheduledAlarms = Flags.startUserBeforeScheduledAlarms()
-                && UserManager.supportsMultipleUsers();
+                && UserManager.supportsMultipleUsers() && Resources.getSystem().getBoolean(
+                com.android.internal.R.bool.config_allowAlarmsOnStoppedUsers);
         if (mStartUserBeforeScheduledAlarms) {
             mUserWakeupStore = new UserWakeupStore();
             mUserWakeupStore.init();
diff --git a/api/api.go b/api/api.go
index cbdb7e8..640773b 100644
--- a/api/api.go
+++ b/api/api.go
@@ -104,8 +104,9 @@
 }
 
 func (a *CombinedApis) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	ctx.WalkDeps(func(child, parent android.Module) bool {
-		if _, ok := android.OtherModuleProvider(ctx, child, java.AndroidLibraryInfoProvider); ok && child.Name() != "framework-res" {
+	ctx.WalkDepsProxy(func(child, parent android.ModuleProxy) bool {
+		javaInfo, ok := android.OtherModuleProvider(ctx, child, java.JavaInfoProvider)
+		if ok && javaInfo.AndroidLibraryDependencyInfo != nil && child.Name() != "framework-res" {
 			// Stubs of BCP and SSCP libraries should not have any dependencies on apps
 			// This check ensures that we do not run into circular dependencies when UNBUNDLED_BUILD_TARGET_SDK_WITH_API_FINGERPRINT=true
 			ctx.ModuleErrorf(
diff --git a/core/api/current.txt b/core/api/current.txt
index b7f7a7f..19f68eb 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -38278,7 +38278,6 @@
     field public static final String ACTION_NOTIFICATION_LISTENER_DETAIL_SETTINGS = "android.settings.NOTIFICATION_LISTENER_DETAIL_SETTINGS";
     field public static final String ACTION_NOTIFICATION_LISTENER_SETTINGS = "android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS";
     field public static final String ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS = "android.settings.NOTIFICATION_POLICY_ACCESS_SETTINGS";
-    field @FlaggedApi("android.provider.system_regional_preferences_api_enabled") public static final String ACTION_NUMBERING_SYSTEM_SETTINGS = "android.settings.NUMBERING_SYSTEM_SETTINGS";
     field public static final String ACTION_PRINT_SETTINGS = "android.settings.ACTION_PRINT_SETTINGS";
     field public static final String ACTION_PRIVACY_SETTINGS = "android.settings.PRIVACY_SETTINGS";
     field public static final String ACTION_PROCESS_WIFI_EASY_CONNECT_URI = "android.settings.PROCESS_WIFI_EASY_CONNECT_URI";
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 16c7017..ae5542b 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3405,12 +3405,12 @@
 
   public final class VirtualDeviceManager {
     method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.companion.virtual.VirtualDeviceManager.VirtualDevice createVirtualDevice(int, @NonNull android.companion.virtual.VirtualDeviceParams);
-    method @FlaggedApi("android.companion.virtual.flags.persistent_device_id_api") @NonNull public java.util.Set<java.lang.String> getAllPersistentDeviceIds();
-    method @FlaggedApi("android.companion.virtual.flags.persistent_device_id_api") @Nullable public CharSequence getDisplayNameForPersistentDeviceId(@NonNull String);
+    method @NonNull public java.util.Set<java.lang.String> getAllPersistentDeviceIds();
+    method @Nullable public CharSequence getDisplayNameForPersistentDeviceId(@NonNull String);
     field public static final int LAUNCH_FAILURE_NO_ACTIVITY = 2; // 0x2
     field public static final int LAUNCH_FAILURE_PENDING_INTENT_CANCELED = 1; // 0x1
     field public static final int LAUNCH_SUCCESS = 0; // 0x0
-    field @FlaggedApi("android.companion.virtual.flags.persistent_device_id_api") public static final String PERSISTENT_DEVICE_ID_DEFAULT = "default:0";
+    field public static final String PERSISTENT_DEVICE_ID_DEFAULT = "default:0";
   }
 
   public static interface VirtualDeviceManager.ActivityListener {
@@ -3432,7 +3432,7 @@
 
   public static class VirtualDeviceManager.VirtualDevice implements java.lang.AutoCloseable {
     method public void addActivityListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
-    method @FlaggedApi("android.companion.virtual.flags.dynamic_policy") public void addActivityPolicyExemption(@NonNull android.content.ComponentName);
+    method public void addActivityPolicyExemption(@NonNull android.content.ComponentName);
     method @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public void addActivityPolicyExemption(@NonNull android.companion.virtual.ActivityPolicyExemption);
     method public void addSoundEffectListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener);
     method public void close();
@@ -3448,7 +3448,7 @@
     method @Deprecated @NonNull public android.hardware.input.VirtualMouse createVirtualMouse(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
     method @NonNull public android.hardware.input.VirtualNavigationTouchpad createVirtualNavigationTouchpad(@NonNull android.hardware.input.VirtualNavigationTouchpadConfig);
     method @FlaggedApi("android.companion.virtualdevice.flags.virtual_rotary") @NonNull public android.hardware.input.VirtualRotaryEncoder createVirtualRotaryEncoder(@NonNull android.hardware.input.VirtualRotaryEncoderConfig);
-    method @FlaggedApi("android.companion.virtual.flags.virtual_stylus") @NonNull public android.hardware.input.VirtualStylus createVirtualStylus(@NonNull android.hardware.input.VirtualStylusConfig);
+    method @NonNull public android.hardware.input.VirtualStylus createVirtualStylus(@NonNull android.hardware.input.VirtualStylusConfig);
     method @NonNull public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.input.VirtualTouchscreenConfig);
     method @Deprecated @NonNull public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
     method public int getDeviceId();
@@ -3458,10 +3458,10 @@
     method public void launchPendingIntent(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
     method public void registerIntentInterceptor(@NonNull android.content.IntentFilter, @NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback);
     method public void removeActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
-    method @FlaggedApi("android.companion.virtual.flags.dynamic_policy") public void removeActivityPolicyExemption(@NonNull android.content.ComponentName);
+    method public void removeActivityPolicyExemption(@NonNull android.content.ComponentName);
     method @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public void removeActivityPolicyExemption(@NonNull android.companion.virtual.ActivityPolicyExemption);
     method public void removeSoundEffectListener(@NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener);
-    method @FlaggedApi("android.companion.virtual.flags.dynamic_policy") public void setDevicePolicy(int, int);
+    method public void setDevicePolicy(int, int);
     method @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public void setDevicePolicy(int, int, int);
     method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") public void setDisplayImePolicy(int, int);
     method public void setShowPointerIcon(boolean);
@@ -3481,7 +3481,7 @@
     method @Deprecated public int getDefaultNavigationPolicy();
     method public int getDevicePolicy(int);
     method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public java.time.Duration getDimDuration();
-    method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") @Nullable public android.content.ComponentName getHomeComponent();
+    method @Nullable public android.content.ComponentName getHomeComponent();
     method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") @Nullable public android.content.ComponentName getInputMethodComponent();
     method public int getLockState();
     method @Nullable public String getName();
@@ -3498,11 +3498,11 @@
     field public static final int LOCK_STATE_DEFAULT = 0; // 0x0
     field @Deprecated public static final int NAVIGATION_POLICY_DEFAULT_ALLOWED = 0; // 0x0
     field @Deprecated public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1; // 0x1
-    field @FlaggedApi("android.companion.virtual.flags.dynamic_policy") public static final int POLICY_TYPE_ACTIVITY = 3; // 0x3
+    field public static final int POLICY_TYPE_ACTIVITY = 3; // 0x3
     field public static final int POLICY_TYPE_AUDIO = 1; // 0x1
     field @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public static final int POLICY_TYPE_BLOCKED_ACTIVITY = 6; // 0x6
     field @FlaggedApi("android.companion.virtual.flags.virtual_camera") public static final int POLICY_TYPE_CAMERA = 5; // 0x5
-    field @FlaggedApi("android.companion.virtual.flags.cross_device_clipboard") public static final int POLICY_TYPE_CLIPBOARD = 4; // 0x4
+    field public static final int POLICY_TYPE_CLIPBOARD = 4; // 0x4
     field @FlaggedApi("android.companion.virtualdevice.flags.default_device_camera_access_policy") public static final int POLICY_TYPE_DEFAULT_DEVICE_CAMERA_ACCESS = 7; // 0x7
     field public static final int POLICY_TYPE_RECENTS = 2; // 0x2
     field public static final int POLICY_TYPE_SENSORS = 0; // 0x0
@@ -3520,7 +3520,7 @@
     method @Deprecated @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDevicePolicy(int, int);
     method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDimDuration(@NonNull java.time.Duration);
-    method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setHomeComponent(@Nullable android.content.ComponentName);
+    method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setHomeComponent(@Nullable android.content.ComponentName);
     method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setInputMethodComponent(@Nullable android.content.ComponentName);
     method @NonNull @RequiresPermission(value=android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY, conditional=true) public android.companion.virtual.VirtualDeviceParams.Builder setLockState(int);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setName(@NonNull String);
@@ -5332,13 +5332,13 @@
 
   public final class VirtualDisplayConfig implements android.os.Parcelable {
     method @FlaggedApi("android.companion.virtualdevice.flags.virtual_display_insets") @Nullable public android.view.DisplayCutout getDisplayCutout();
-    method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") public boolean isHomeSupported();
+    method public boolean isHomeSupported();
     method @FlaggedApi("com.android.window.flags.vdm_force_app_universal_resizable_api") public boolean isIgnoreActivitySizeRestrictions();
   }
 
   public static final class VirtualDisplayConfig.Builder {
     method @FlaggedApi("android.companion.virtualdevice.flags.virtual_display_insets") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setDisplayCutout(@Nullable android.view.DisplayCutout);
-    method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setHomeSupported(boolean);
+    method @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setHomeSupported(boolean);
     method @FlaggedApi("com.android.window.flags.vdm_force_app_universal_resizable_api") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setIgnoreActivitySizeRestrictions(boolean);
   }
 
@@ -5970,13 +5970,13 @@
     method @NonNull public android.hardware.input.VirtualRotaryEncoderScrollEvent.Builder setScrollAmount(@FloatRange(from=-1.0F, to=1.0f) float);
   }
 
-  @FlaggedApi("android.companion.virtual.flags.virtual_stylus") public class VirtualStylus implements java.io.Closeable {
+  public class VirtualStylus implements java.io.Closeable {
     method public void close();
     method public void sendButtonEvent(@NonNull android.hardware.input.VirtualStylusButtonEvent);
     method public void sendMotionEvent(@NonNull android.hardware.input.VirtualStylusMotionEvent);
   }
 
-  @FlaggedApi("android.companion.virtual.flags.virtual_stylus") public final class VirtualStylusButtonEvent implements android.os.Parcelable {
+  public final class VirtualStylusButtonEvent implements android.os.Parcelable {
     method public int describeContents();
     method public int getAction();
     method public int getButtonCode();
@@ -5989,7 +5989,7 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualStylusButtonEvent> CREATOR;
   }
 
-  @FlaggedApi("android.companion.virtual.flags.virtual_stylus") public static final class VirtualStylusButtonEvent.Builder {
+  public static final class VirtualStylusButtonEvent.Builder {
     ctor public VirtualStylusButtonEvent.Builder();
     method @NonNull public android.hardware.input.VirtualStylusButtonEvent build();
     method @NonNull public android.hardware.input.VirtualStylusButtonEvent.Builder setAction(int);
@@ -5997,7 +5997,7 @@
     method @NonNull public android.hardware.input.VirtualStylusButtonEvent.Builder setEventTimeNanos(long);
   }
 
-  @FlaggedApi("android.companion.virtual.flags.virtual_stylus") public final class VirtualStylusConfig extends android.hardware.input.VirtualInputDeviceConfig implements android.os.Parcelable {
+  public final class VirtualStylusConfig extends android.hardware.input.VirtualInputDeviceConfig implements android.os.Parcelable {
     method public int describeContents();
     method public int getHeight();
     method public int getWidth();
@@ -6005,12 +6005,12 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualStylusConfig> CREATOR;
   }
 
-  @FlaggedApi("android.companion.virtual.flags.virtual_stylus") public static final class VirtualStylusConfig.Builder extends android.hardware.input.VirtualInputDeviceConfig.Builder<android.hardware.input.VirtualStylusConfig.Builder> {
+  public static final class VirtualStylusConfig.Builder extends android.hardware.input.VirtualInputDeviceConfig.Builder<android.hardware.input.VirtualStylusConfig.Builder> {
     ctor public VirtualStylusConfig.Builder(@IntRange(from=1) int, @IntRange(from=1) int);
     method @NonNull public android.hardware.input.VirtualStylusConfig build();
   }
 
-  @FlaggedApi("android.companion.virtual.flags.virtual_stylus") public final class VirtualStylusMotionEvent implements android.os.Parcelable {
+  public final class VirtualStylusMotionEvent implements android.os.Parcelable {
     method public int describeContents();
     method public int getAction();
     method public long getEventTimeNanos();
@@ -6029,7 +6029,7 @@
     field public static final int TOOL_TYPE_STYLUS = 2; // 0x2
   }
 
-  @FlaggedApi("android.companion.virtual.flags.virtual_stylus") public static final class VirtualStylusMotionEvent.Builder {
+  public static final class VirtualStylusMotionEvent.Builder {
     ctor public VirtualStylusMotionEvent.Builder();
     method @NonNull public android.hardware.input.VirtualStylusMotionEvent build();
     method @NonNull public android.hardware.input.VirtualStylusMotionEvent.Builder setAction(int);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index a352d9d..b118c7b 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -436,34 +436,6 @@
     ctor public PictureInPictureUiState(boolean);
   }
 
-  public class PropertyInvalidatedCache<Query, Result> {
-    ctor public PropertyInvalidatedCache(int, @NonNull String, @NonNull String, @NonNull String, @NonNull android.app.PropertyInvalidatedCache.QueryHandler<Query,Result>);
-    method @NonNull public static String createPropertyName(@NonNull String, @NonNull String);
-    method public void disableForCurrentProcess();
-    method public static void disableForCurrentProcess(@NonNull String);
-    method public static void disableForTestMode();
-    method public final void disableInstance();
-    method public final void disableSystemWide();
-    method public final void forgetDisableLocal();
-    method public boolean getDisabledState();
-    method public void invalidateCache();
-    method public static void invalidateCache(@NonNull String, @NonNull String);
-    method public final boolean isDisabled();
-    method @Nullable public Result query(@NonNull Query);
-    method public static void setTestMode(boolean);
-    method public void testPropertyName();
-    field public static final String MODULE_BLUETOOTH = "bluetooth";
-    field public static final String MODULE_SYSTEM = "system_server";
-    field public static final String MODULE_TELEPHONY = "telephony";
-    field public static final String MODULE_TEST = "test";
-  }
-
-  public abstract static class PropertyInvalidatedCache.QueryHandler<Q, R> {
-    ctor public PropertyInvalidatedCache.QueryHandler();
-    method @Nullable public abstract R apply(@NonNull Q);
-    method public boolean shouldBypassCache(@NonNull Q);
-  }
-
   public class StatusBarManager {
     method public void cancelRequestAddTile(@NonNull String);
     method public void clickNotification(@Nullable String, int, int, boolean);
@@ -2444,17 +2416,28 @@
     method @FlaggedApi("android.os.mainline_vcn_platform_api") public final void removeEqualMessages(int, @Nullable Object);
   }
 
-  public class IpcDataCache<Query, Result> extends android.app.PropertyInvalidatedCache<Query,Result> {
+  public class IpcDataCache<Query, Result> {
     ctor public IpcDataCache(int, @NonNull String, @NonNull String, @NonNull String, @NonNull android.os.IpcDataCache.QueryHandler<Query,Result>);
+    method public void disableForCurrentProcess();
     method public static void disableForCurrentProcess(@NonNull String);
+    method public final void disableInstance();
+    method public final void disableSystemWide();
+    method public final void forgetDisableLocal();
+    method public boolean getDisabledState();
+    method public void invalidateCache();
     method public static void invalidateCache(@NonNull String, @NonNull String);
+    method public final boolean isDisabled();
+    method @Nullable public Result query(@NonNull Query);
+    method public static void setTestMode(boolean);
     field public static final String MODULE_BLUETOOTH = "bluetooth";
     field public static final String MODULE_SYSTEM = "system_server";
     field public static final String MODULE_TEST = "test";
   }
 
-  public abstract static class IpcDataCache.QueryHandler<Q, R> extends android.app.PropertyInvalidatedCache.QueryHandler<Q,R> {
+  public abstract static class IpcDataCache.QueryHandler<Q, R> {
     ctor public IpcDataCache.QueryHandler();
+    method @Nullable public abstract R apply(@NonNull Q);
+    method public boolean shouldBypassCache(@NonNull Q);
   }
 
   public final class MessageQueue {
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index af6978a..82c746a 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -1846,6 +1846,7 @@
     }
 
     /** @hide */
+    @WindowConfiguration.WindowingMode
     public int getLaunchWindowingMode() {
         return mLaunchWindowingMode;
     }
@@ -1855,7 +1856,7 @@
      * @hide
      */
     @TestApi
-    public void setLaunchWindowingMode(int windowingMode) {
+    public void setLaunchWindowingMode(@WindowConfiguration.WindowingMode int windowingMode) {
         mLaunchWindowingMode = windowingMode;
     }
 
diff --git a/core/java/android/app/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java
index f34341f..3214bd8 100644
--- a/core/java/android/app/ApplicationStartInfo.java
+++ b/core/java/android/app/ApplicationStartInfo.java
@@ -729,6 +729,7 @@
         return 0;
     }
 
+    // LINT.IfChange(write_parcel)
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeInt(mStartupState);
@@ -753,6 +754,7 @@
         dest.writeLong(mMonoticCreationTimeMs);
         dest.writeInt(mStartComponent);
     }
+    // LINT.ThenChange(:read_parcel)
 
     /** @hide */
     public ApplicationStartInfo(long monotonicCreationTimeMs) {
@@ -779,6 +781,7 @@
     }
 
     /** @hide */
+    // LINT.IfChange(read_parcel)
     @VisibleForTesting
     public ApplicationStartInfo(@NonNull Parcel in) {
         mStartupState = in.readInt();
@@ -803,6 +806,7 @@
         mMonoticCreationTimeMs = in.readLong();
         mStartComponent = in.readInt();
     }
+    // LINT.ThenChange(:write_parcel)
 
     private static String intern(@Nullable String source) {
         return source != null ? source.intern() : null;
@@ -835,6 +839,7 @@
      * @param fieldId Field Id of the ApplicationStartInfo as defined in the parent message
      * @hide
      */
+    // LINT.IfChange(write_proto)
     public void writeToProto(ProtoOutputStream proto, long fieldId) throws IOException {
         final long token = proto.start(fieldId);
         proto.write(ApplicationStartInfoProto.PID, mPid);
@@ -884,6 +889,7 @@
         proto.write(ApplicationStartInfoProto.START_COMPONENT, mStartComponent);
         proto.end(token);
     }
+    // LINT.ThenChange(:read_proto)
 
     /**
      * Read from a protocol buffer input stream. Protocol buffer message definition at {@link
@@ -893,6 +899,7 @@
      * @param fieldId Field Id of the ApplicationStartInfo as defined in the parent message
      * @hide
      */
+    // LINT.IfChange(read_proto)
     public void readFromProto(ProtoInputStream proto, long fieldId)
             throws IOException, WireTypeMismatchException, ClassNotFoundException {
         final long token = proto.start(fieldId);
@@ -976,6 +983,7 @@
         }
         proto.end(token);
     }
+    // LINT.ThenChange(:write_proto)
 
     /** @hide */
     public void dump(@NonNull PrintWriter pw, @Nullable String prefix, @Nullable String seqSuffix,
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 0451ac0..1738a92 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -114,6 +114,7 @@
     NotificationChannel getNotificationChannelForPackage(String pkg, int uid, String channelId, String conversationId, boolean includeDeleted);
     void deleteNotificationChannel(String pkg, String channelId);
     ParceledListSlice getNotificationChannels(String callingPkg, String targetPkg, int userId);
+    ParceledListSlice getOrCreateNotificationChannels(String callingPkg, String targetPkg, int userId, boolean createPrefsIfNeeded);
     ParceledListSlice getNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted);
     int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted);
     int getDeletedChannelCount(String pkg, int uid);
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index aede8aa..24f2495 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -68,6 +68,7 @@
 import android.service.notification.ZenDeviceEffects;
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenPolicy;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.LruCache;
 import android.util.Slog;
@@ -77,6 +78,8 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.time.Duration;
+import java.time.Instant;
 import java.time.InstantSource;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -661,8 +664,10 @@
             mCallNotificationEventCallbacks = new HashMap<>();
 
     private final InstantSource mClock;
-    private final RateEstimator mUpdateRateEstimator = new RateEstimator();
-    private final RateEstimator mUnnecessaryCancelRateEstimator = new RateEstimator();
+    private final RateLimiter mUpdateRateLimiter = new RateLimiter("notify (update)",
+            MAX_NOTIFICATION_UPDATE_RATE);
+    private final RateLimiter mUnnecessaryCancelRateLimiter = new RateLimiter("cancel (dupe)",
+            MAX_NOTIFICATION_UNNECESSARY_CANCEL_RATE);
     // Value is KNOWN_STATUS_ENQUEUED/_CANCELLED
     private final LruCache<NotificationKey, Integer> mKnownNotifications = new LruCache<>(100);
     private final Object mThrottleLock = new Object();
@@ -820,21 +825,16 @@
 
         if (Flags.nmBinderPerfThrottleNotify()) {
             NotificationKey key = new NotificationKey(user, pkg, tag, id);
-            long now = mClock.millis();
             synchronized (mThrottleLock) {
                 Integer status = mKnownNotifications.get(key);
                 if (status != null && status == KNOWN_STATUS_ENQUEUED
                         && !notification.hasCompletedProgress()) {
-                    float updateRate = mUpdateRateEstimator.getRate(now);
-                    if (updateRate > MAX_NOTIFICATION_UPDATE_RATE) {
-                        Slog.w(TAG, "Shedding update of " + key
-                                + ", notification update maximum rate exceeded (" + updateRate
-                                + ")");
+                    if (mUpdateRateLimiter.eventExceedsRate()) {
+                        mUpdateRateLimiter.recordRejected(key);
                         return true;
                     }
-                    mUpdateRateEstimator.update(now);
+                    mUpdateRateLimiter.recordAccepted();
                 }
-
                 mKnownNotifications.put(key, KNOWN_STATUS_ENQUEUED);
             }
         }
@@ -845,6 +845,51 @@
     private record NotificationKey(@NonNull UserHandle user, @NonNull String pkg,
                                    @Nullable String tag, int id) { }
 
+    /** Helper class to rate-limit Binder calls. */
+    private class RateLimiter {
+
+        private static final Duration RATE_LIMITER_LOG_INTERVAL = Duration.ofSeconds(5);
+
+        private final RateEstimator mInputRateEstimator;
+        private final RateEstimator mOutputRateEstimator;
+        private final String mName;
+        private final float mLimitRate;
+
+        private Instant mLogSilencedUntil;
+
+        private RateLimiter(String name, float limitRate) {
+            mInputRateEstimator = new RateEstimator();
+            mOutputRateEstimator = new RateEstimator();
+            mName = name;
+            mLimitRate = limitRate;
+        }
+
+        boolean eventExceedsRate() {
+            long nowMillis = mClock.millis();
+            mInputRateEstimator.update(nowMillis);
+            return mOutputRateEstimator.getRate(nowMillis) > mLimitRate;
+        }
+
+        void recordAccepted() {
+            mOutputRateEstimator.update(mClock.millis());
+        }
+
+        void recordRejected(NotificationKey key) {
+            Instant now = mClock.instant();
+            if (mLogSilencedUntil != null && now.isBefore(mLogSilencedUntil)) {
+                return;
+            }
+
+            long nowMillis = now.toEpochMilli();
+            Slog.w(TAG, TextUtils.formatSimple(
+                    "Shedding %s of %s, rate limit (%s) exceeded: input %s, output would be %s",
+                    mName, key, mLimitRate, mInputRateEstimator.getRate(nowMillis),
+                    mOutputRateEstimator.getRate(nowMillis)));
+
+            mLogSilencedUntil = now.plus(RATE_LIMITER_LOG_INTERVAL);
+        }
+    }
+
     private Notification fixNotification(Notification notification) {
         String pkg = mContext.getPackageName();
         // Fix the notification as best we can.
@@ -967,18 +1012,14 @@
     private boolean discardCancel(UserHandle user, String pkg, @Nullable String tag, int id) {
         if (Flags.nmBinderPerfThrottleNotify()) {
             NotificationKey key = new NotificationKey(user, pkg, tag, id);
-            long now = mClock.millis();
             synchronized (mThrottleLock) {
                 Integer status = mKnownNotifications.get(key);
                 if (status != null && status == KNOWN_STATUS_CANCELLED) {
-                    float cancelRate = mUnnecessaryCancelRateEstimator.getRate(now);
-                    if (cancelRate > MAX_NOTIFICATION_UNNECESSARY_CANCEL_RATE) {
-                        Slog.w(TAG, "Shedding cancel of " + key
-                                + ", presumably unnecessary and maximum rate exceeded ("
-                                + cancelRate + ")");
+                    if (mUnnecessaryCancelRateLimiter.eventExceedsRate()) {
+                        mUnnecessaryCancelRateLimiter.recordRejected(key);
                         return true;
                     }
-                    mUnnecessaryCancelRateEstimator.update(now);
+                    mUnnecessaryCancelRateLimiter.recordAccepted();
                 }
                 mKnownNotifications.put(key, KNOWN_STATUS_CANCELLED);
             }
@@ -1211,7 +1252,8 @@
                     mNotificationChannelListCache.query(new NotificationChannelQuery(
                             mContext.getOpPackageName(),
                             mContext.getPackageName(),
-                            mContext.getUserId())));
+                            mContext.getUserId(),
+                            true)));  // create (default channel) if needed
         } else {
             INotificationManager service = service();
             try {
@@ -1239,7 +1281,8 @@
                     mNotificationChannelListCache.query(new NotificationChannelQuery(
                             mContext.getOpPackageName(),
                             mContext.getPackageName(),
-                            mContext.getUserId())));
+                            mContext.getUserId(),
+                            true)));  // create (default channel) if needed
         } else {
             INotificationManager service = service();
             try {
@@ -1263,9 +1306,10 @@
     public List<NotificationChannel> getNotificationChannels() {
         if (Flags.nmBinderPerfCacheChannels()) {
             return mNotificationChannelListCache.query(new NotificationChannelQuery(
-               mContext.getOpPackageName(),
-               mContext.getPackageName(),
-               mContext.getUserId()));
+                    mContext.getOpPackageName(),
+                    mContext.getPackageName(),
+                    mContext.getUserId(),
+                    false));
         } else {
             INotificationManager service = service();
             try {
@@ -1405,8 +1449,8 @@
                 public List<NotificationChannel> apply(NotificationChannelQuery query) {
                     INotificationManager service = service();
                     try {
-                        return service.getNotificationChannels(query.callingPkg,
-                                query.targetPkg, query.userId).getList();
+                        return service.getOrCreateNotificationChannels(query.callingPkg,
+                                query.targetPkg, query.userId, query.createIfNeeded).getList();
                     } catch (RemoteException e) {
                         throw e.rethrowFromSystemServer();
                     }
@@ -1434,7 +1478,8 @@
     private record NotificationChannelQuery(
             String callingPkg,
             String targetPkg,
-            int userId) {}
+            int userId,
+            boolean createIfNeeded) {}
 
     /**
      * @hide
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index e7e9b00..660d880 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -22,7 +22,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.TestApi;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.Looper;
@@ -77,7 +76,6 @@
  * @param <Result> The class holding cache entries; use a boxed primitive if possible
  * @hide
  */
-@TestApi
 @android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class PropertyInvalidatedCache<Query, Result> {
     /**
@@ -95,7 +93,6 @@
      * This is a configuration class that customizes a cache instance.
      * @hide
      */
-    @TestApi
     public static abstract class QueryHandler<Q,R> {
         /**
          * Compute a result given a query.  The semantics are those of Functor.
@@ -134,7 +131,6 @@
      * the system has permissions to write properties with this module.
      * @hide
      */
-    @TestApi
     public static final String MODULE_TEST = "test";
 
     /**
@@ -142,18 +138,17 @@
      * the system processes.
      * @hide
      */
-    @TestApi
     public static final String MODULE_SYSTEM = "system_server";
 
     /**
      * The module used for bluetooth caches.
      * @hide
      */
-    @TestApi
     public static final String MODULE_BLUETOOTH = "bluetooth";
 
     /**
      * The module used for telephony caches.
+     * @hide
      */
     public static final String MODULE_TELEPHONY = "telephony";
 
@@ -171,7 +166,6 @@
      * error message.
      * @hide
      */
-    @TestApi
     public static @NonNull String createPropertyName(@NonNull String module,
             @NonNull String apiName) {
         char[] api = apiName.toCharArray();
@@ -1382,7 +1376,6 @@
      * @param computer The code to compute values that are not in the cache.
      * @hide
      */
-    @TestApi
     public PropertyInvalidatedCache(int maxEntries, @NonNull String module, @NonNull String api,
             @NonNull String cacheName, @NonNull QueryHandler<Query, Result> computer) {
         this(new Args(module).maxEntries(maxEntries).api(api), cacheName, computer);
@@ -1409,7 +1402,7 @@
      * current logic does not care.
      * @hide
      */
-    @TestApi
+    @VisibleForTesting
     public static void setTestMode(boolean mode) {
         synchronized (sGlobalLock) {
             if (sTestMode == mode) {
@@ -1450,7 +1443,6 @@
      * must be true when this method is called.
      * @hide
      */
-    @TestApi
     public void testPropertyName() {
         synchronized (sGlobalLock) {
             if (sTestMode == false) {
@@ -1542,8 +1534,8 @@
      * be re-enabled.
      * @hide
      */
-    @TestApi
-    public final void disableInstance() {
+    @VisibleForTesting
+    public void disableInstance() {
         synchronized (mLock) {
             mDisabled = true;
             clear();
@@ -1579,8 +1571,8 @@
      * found in the list of disabled caches.
      * @hide
      */
-    @TestApi
-    public final void forgetDisableLocal() {
+    @VisibleForTesting
+    public void forgetDisableLocal() {
         synchronized (sGlobalLock) {
             sDisabledKeys.remove(mCacheName);
         }
@@ -1603,13 +1595,11 @@
      * property.  Once disabled, a cache cannot be reenabled.
      * @hide
      */
-    @TestApi
     public void disableForCurrentProcess() {
         disableLocal(mCacheName);
     }
 
     /** @hide */
-    @TestApi
     public static void disableForCurrentProcess(@NonNull String cacheName) {
         disableLocal(cacheName);
     }
@@ -1618,8 +1608,8 @@
      * Return whether a cache instance is disabled.
      * @hide
      */
-    @TestApi
-    public final boolean isDisabled() {
+    @VisibleForTesting
+    public boolean isDisabled() {
         return mDisabled || !sEnabled;
     }
 
@@ -1627,7 +1617,6 @@
      * Get a value from the cache or recompute it.
      * @hide
      */
-    @TestApi
     public @Nullable Result query(@NonNull Query query) {
         // Let access to mDisabled race: it's atomic anyway.
         long currentNonce = (!isDisabled()) ? getCurrentNonce() : NONCE_DISABLED;
@@ -1767,8 +1756,8 @@
      * just use the static version of this function.
      * @hide
      */
-    @TestApi
-    public final void disableSystemWide() {
+    @VisibleForTesting
+    public void disableSystemWide() {
         disableSystemWide(mPropertyName);
     }
 
@@ -1788,7 +1777,6 @@
      * to look up the NonceHandler for a given property name.
      * @hide
      */
-    @TestApi
     public void invalidateCache() {
         mNonce.invalidate();
     }
@@ -1817,7 +1805,6 @@
      * Invalidate caches in all processes that are keyed for the module and api.
      * @hide
      */
-    @TestApi
     public static void invalidateCache(@NonNull String module, @NonNull String api) {
         invalidateCache(createPropertyName(module, api));
     }
@@ -2059,7 +2046,6 @@
      * temporarily disable caching, use the corking mechanism.
      * @hide
      */
-    @TestApi
     public static void disableForTestMode() {
         Log.d(TAG, "disabling all caches in the process");
         sEnabled = false;
@@ -2068,10 +2054,8 @@
     /**
      * Report the disabled status of this cache instance.  The return value does not
      * reflect status of the property key.
-     * @hide
      */
-    @TestApi
-    public boolean getDisabledState() {
+    private boolean getDisabledState() {
         return isDisabled();
     }
 
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index b1db137..edd17e8 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -13,6 +13,13 @@
 }
 
 flag {
+  name: "notifications_redesign_themed_app_icons"
+  namespace: "systemui"
+  description: "Notifications Redesign: Experiment to make app icons in notifications themed"
+  bug: "371174789"
+}
+
+flag {
   name: "notifications_redesign_templates"
   namespace: "systemui"
   description: "Notifications Redesign: Update notification templates"
@@ -174,16 +181,6 @@
 }
 
 flag {
-  name: "update_ranking_time"
-  namespace: "systemui"
-  description: "Updates notification sorting criteria to highlight new content while maintaining stability"
-  bug: "326016985"
-  metadata {
-    purpose: PURPOSE_BUGFIX
-  }
-}
-
-flag {
   name: "sort_section_by_time"
   namespace: "systemui"
   description: "Changes notification sort order to be by time within a section"
diff --git a/packages/CrashRecovery/framework/java/android/service/watchdog/PackageConfig.aidl b/core/java/android/app/supervision/ISupervisionAppService.aidl
similarity index 81%
rename from packages/CrashRecovery/framework/java/android/service/watchdog/PackageConfig.aidl
rename to core/java/android/app/supervision/ISupervisionAppService.aidl
index 0131586..033998f 100644
--- a/packages/CrashRecovery/framework/java/android/service/watchdog/PackageConfig.aidl
+++ b/core/java/android/app/supervision/ISupervisionAppService.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,9 +14,10 @@
  * limitations under the License.
  */
 
-package android.service.watchdog;
+package android.app.supervision;
 
 /**
  * @hide
  */
-parcelable PackageConfig;
+interface ISupervisionAppService {
+}
diff --git a/core/java/android/app/supervision/SupervisionAppService.java b/core/java/android/app/supervision/SupervisionAppService.java
new file mode 100644
index 0000000..4468c78
--- /dev/null
+++ b/core/java/android/app/supervision/SupervisionAppService.java
@@ -0,0 +1,37 @@
+/*
+ * 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 android.app.supervision;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+/**
+ * Base class for a service that the {@code android.app.role.RoleManager.ROLE_SYSTEM_SUPERVISION}
+ * role holder must implement.
+ *
+ * @hide
+ */
+public class SupervisionAppService extends Service {
+    private final ISupervisionAppService mBinder = new ISupervisionAppService.Stub() {
+    };
+
+    @Override
+    public final IBinder onBind(Intent intent) {
+        return mBinder.asBinder();
+    }
+}
diff --git a/core/java/android/app/supervision/flags.aconfig b/core/java/android/app/supervision/flags.aconfig
index 1b03532..4ee3a03 100644
--- a/core/java/android/app/supervision/flags.aconfig
+++ b/core/java/android/app/supervision/flags.aconfig
@@ -32,3 +32,11 @@
   description: "Flag that deprecates supervision methods in DPM"
   bug: "382034839"
 }
+
+flag {
+  name: "enable_supervision_app_service"
+  is_exported: true
+  namespace: "supervision"
+  description: "Flag to enable the SupervisionAppService"
+  bug: "389123070"
+}
diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java
index 311e24b..3ef78af 100644
--- a/core/java/android/companion/virtual/VirtualDeviceInternal.java
+++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java
@@ -32,7 +32,6 @@
 import android.companion.virtual.camera.VirtualCamera;
 import android.companion.virtual.camera.VirtualCameraConfig;
 import android.companion.virtual.sensor.VirtualSensor;
-import android.companion.virtualdevice.flags.Flags;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -473,14 +472,12 @@
             @Nullable VirtualAudioDevice.AudioConfigurationChangeCallback callback) {
         if (mVirtualAudioDevice == null) {
             try {
-                Context context = mContext;
-                if (Flags.deviceAwareRecordAudioPermission()) {
-                    // When using a default policy for audio device-aware RECORD_AUDIO permission
-                    // should not take effect, thus register policies with the default context.
-                    if (mVirtualDevice.getDevicePolicy(POLICY_TYPE_AUDIO) == DEVICE_POLICY_CUSTOM) {
-                        context = mContext.createDeviceContext(getDeviceId());
-                    }
-                }
+                // When using a default policy for audio, the device-aware RECORD_AUDIO permission
+                // should not take effect, thus register policies with the default context.
+                final Context context =
+                        mVirtualDevice.getDevicePolicy(POLICY_TYPE_AUDIO) == DEVICE_POLICY_CUSTOM
+                                ? mContext.createDeviceContext(getDeviceId())
+                                : mContext;
                 mVirtualAudioDevice = new VirtualAudioDevice(context, mVirtualDevice, display,
                         executor, callback, () -> mVirtualAudioDevice = null);
             } catch (RemoteException e) {
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 73ea9f0..91ea673 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -170,7 +170,6 @@
      * @hide
      */
     @SystemApi
-    @FlaggedApi(Flags.FLAG_PERSISTENT_DEVICE_ID_API)
     public static final String PERSISTENT_DEVICE_ID_DEFAULT =
             "default:" + Context.DEVICE_ID_DEFAULT;
 
@@ -393,7 +392,6 @@
      * @hide
      */
     // TODO(b/315481938): Link @see VirtualDevice#getPersistentDeviceId()
-    @FlaggedApi(Flags.FLAG_PERSISTENT_DEVICE_ID_API)
     @SystemApi
     @Nullable
     public CharSequence getDisplayNameForPersistentDeviceId(@NonNull String persistentDeviceId) {
@@ -416,7 +414,6 @@
      * @hide
      */
     // TODO(b/315481938): Link @see VirtualDevice#getPersistentDeviceId()
-    @FlaggedApi(Flags.FLAG_PERSISTENT_DEVICE_ID_API)
     @SystemApi
     @NonNull
     public Set<String> getAllPersistentDeviceIds() {
@@ -780,7 +777,6 @@
          * @see VirtualDeviceParams#POLICY_TYPE_RECENTS
          * @see VirtualDeviceParams#POLICY_TYPE_ACTIVITY
          */
-        @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY)
         public void setDevicePolicy(@VirtualDeviceParams.DynamicPolicyType int policyType,
                 @VirtualDeviceParams.DevicePolicy int devicePolicy) {
             mVirtualDeviceInternal.setDevicePolicy(policyType, devicePolicy);
@@ -802,7 +798,6 @@
          * @see #removeActivityPolicyExemption(ComponentName)
          * @see #setDevicePolicy
          */
-        @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY)
         public void addActivityPolicyExemption(@NonNull ComponentName componentName) {
             addActivityPolicyExemption(new ActivityPolicyExemption.Builder()
                     .setComponentName(componentName)
@@ -825,7 +820,6 @@
          * @see #addActivityPolicyExemption(ComponentName)
          * @see #setDevicePolicy
          */
-        @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY)
         public void removeActivityPolicyExemption(@NonNull ComponentName componentName) {
             removeActivityPolicyExemption(new ActivityPolicyExemption.Builder()
                     .setComponentName(componentName)
@@ -1037,9 +1031,7 @@
          * @param config the touchscreen configurations for the virtual stylus.
          */
         @NonNull
-        @FlaggedApi(Flags.FLAG_VIRTUAL_STYLUS)
-        public VirtualStylus createVirtualStylus(
-                @NonNull VirtualStylusConfig config) {
+        public VirtualStylus createVirtualStylus(@NonNull VirtualStylusConfig config) {
             return mVirtualDeviceInternal.createVirtualStylus(config);
         }
 
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index 2be27da..761e75b 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -248,7 +248,6 @@
      */
     // TODO(b/333443509): Update the documentation of custom policy and link to the new policy
     // POLICY_TYPE_BLOCKED_ACTIVITY
-    @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY)
     public static final int POLICY_TYPE_ACTIVITY = 3;
 
     /**
@@ -264,7 +263,6 @@
      *
      * @see android.hardware.display.DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED
      */
-    @FlaggedApi(Flags.FLAG_CROSS_DEVICE_CLIPBOARD)
     public static final int POLICY_TYPE_CLIPBOARD = 4;
 
     /**
@@ -431,7 +429,6 @@
      * @see Builder#setHomeComponent
      * @see VirtualDisplayConfig#isHomeSupported()
      */
-    @FlaggedApi(Flags.FLAG_VDM_CUSTOM_HOME)
     @Nullable
     public ComponentName getHomeComponent() {
         return mHomeComponent;
@@ -926,7 +923,6 @@
          *
          * @see VirtualDisplayConfig#isHomeSupported()
          */
-        @FlaggedApi(Flags.FLAG_VDM_CUSTOM_HOME)
         @NonNull
         public Builder setHomeComponent(@Nullable ComponentName homeComponent) {
             mHomeComponent = homeComponent;
@@ -1282,33 +1278,31 @@
                         mVirtualSensorDirectChannelCallback);
             }
 
-            if (Flags.dynamicPolicy()) {
-                switch (mDevicePolicies.get(POLICY_TYPE_ACTIVITY, -1)) {
-                    case DEVICE_POLICY_DEFAULT:
-                        if (mDefaultActivityPolicyConfigured
-                                && mDefaultActivityPolicy == ACTIVITY_POLICY_DEFAULT_BLOCKED) {
-                            throw new IllegalArgumentException(
-                                    "DEVICE_POLICY_DEFAULT is explicitly configured for "
-                                            + "POLICY_TYPE_ACTIVITY, which is exclusive with "
-                                            + "setAllowedActivities.");
-                        }
-                        break;
-                    case DEVICE_POLICY_CUSTOM:
-                        if (mDefaultActivityPolicyConfigured
-                                && mDefaultActivityPolicy == ACTIVITY_POLICY_DEFAULT_ALLOWED) {
-                            throw new IllegalArgumentException(
-                                    "DEVICE_POLICY_CUSTOM is explicitly configured for "
-                                            + "POLICY_TYPE_ACTIVITY, which is exclusive with "
-                                            + "setBlockedActivities.");
-                        }
-                        break;
-                    default:
-                        if (mDefaultActivityPolicyConfigured
-                                && mDefaultActivityPolicy == ACTIVITY_POLICY_DEFAULT_BLOCKED) {
-                            mDevicePolicies.put(POLICY_TYPE_ACTIVITY, DEVICE_POLICY_CUSTOM);
-                        }
-                        break;
-                }
+            switch (mDevicePolicies.get(POLICY_TYPE_ACTIVITY, -1)) {
+                case DEVICE_POLICY_DEFAULT:
+                    if (mDefaultActivityPolicyConfigured
+                            && mDefaultActivityPolicy == ACTIVITY_POLICY_DEFAULT_BLOCKED) {
+                        throw new IllegalArgumentException(
+                                "DEVICE_POLICY_DEFAULT is explicitly configured for "
+                                        + "POLICY_TYPE_ACTIVITY, which is exclusive with "
+                                        + "setAllowedActivities.");
+                    }
+                    break;
+                case DEVICE_POLICY_CUSTOM:
+                    if (mDefaultActivityPolicyConfigured
+                            && mDefaultActivityPolicy == ACTIVITY_POLICY_DEFAULT_ALLOWED) {
+                        throw new IllegalArgumentException(
+                                "DEVICE_POLICY_CUSTOM is explicitly configured for "
+                                        + "POLICY_TYPE_ACTIVITY, which is exclusive with "
+                                        + "setBlockedActivities.");
+                    }
+                    break;
+                default:
+                    if (mDefaultActivityPolicyConfigured
+                            && mDefaultActivityPolicy == ACTIVITY_POLICY_DEFAULT_BLOCKED) {
+                        mDevicePolicies.put(POLICY_TYPE_ACTIVITY, DEVICE_POLICY_CUSTOM);
+                    }
+                    break;
             }
 
             if (mDimDuration.compareTo(mScreenOffTimeout) > 0) {
@@ -1319,10 +1313,6 @@
                 mScreenOffTimeout = INFINITE_TIMEOUT;
             }
 
-            if (!Flags.crossDeviceClipboard()) {
-                mDevicePolicies.delete(POLICY_TYPE_CLIPBOARD);
-            }
-
             if (!Flags.virtualCamera()) {
                 mDevicePolicies.delete(POLICY_TYPE_CAMERA);
             }
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index 84af840..6da2a07 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -19,14 +19,6 @@
 
 flag {
      namespace: "virtual_devices"
-     name: "device_aware_record_audio_permission"
-     description: "Enable device-aware RECORD_AUDIO permission"
-     bug: "291737188"
-     is_fixed_read_only: true
-}
-
-flag {
-     namespace: "virtual_devices"
      name: "media_projection_keyguard_restrictions"
      description: "Auto-stop MP when the device locks"
      bug: "348335290"
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 01e24d81..885a2db 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -4215,6 +4215,17 @@
     public static final String ACTION_USER_INFO_CHANGED =
             "android.intent.action.USER_INFO_CHANGED";
 
+
+    /**
+     * Broadcast sent to the system when a user's information changes. Carries an extra
+     * {@link #EXTRA_USER_HANDLE} to indicate which user's information changed.
+     * This is only sent to permission protected manifest receivers. It is sent to all users.
+     * @hide
+     */
+    @BroadcastBehavior(includeBackground = true)
+    public static final String ACTION_USER_INFO_CHANGED_BACKGROUND =
+            "android.intent.action.USER_INFO_CHANGED_BACKGROUND";
+
     /**
      * Broadcast sent to the primary user when an associated managed profile is added (the profile
      * was created and is ready to be used). Carries an extra {@link #EXTRA_USER} that specifies
@@ -5460,7 +5471,7 @@
     /**
      * Activities that can be safely invoked from a browser must support this
      * category.  For example, if the user is viewing a web page or an e-mail
-     * and clicks on a link in the text, the Intent generated execute that
+     * and clicks on a link in the text, the Intent generated to execute that
      * link will require the BROWSABLE category, so that only activities
      * supporting this category will be considered as possible actions.  By
      * supporting this category, you are promising that there is nothing
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index a0c0f12..1724d9f 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -1932,6 +1932,9 @@
      * caller should have normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES}
      * permission and the {@link android.app.role.RoleManager#ROLE_HOME} role.
      *
+     * <p>This callback will also receive changes to the {@link LauncherUserInfo#getUserConfig()},
+     * allowing clients to monitor updates to the user-specific configuration.
+     *
      * @param callback The callback to register.
      */
     // Alternatively, a system app can access this api for private profile if they've been granted
@@ -1950,6 +1953,9 @@
      * caller should have normal {@link android.Manifest.permission#ACCESS_HIDDEN_PROFILES}
      * permission and the {@link android.app.role.RoleManager#ROLE_HOME} role.
      *
+     * <p>This callback will also receive changes to the {@link LauncherUserInfo#getUserConfig()},
+     * allowing clients to monitor updates to the user-specific configuration.
+     *
      * @param callback The callback to register.
      * @param handler that should be used to post callbacks on, may be null.
      */
diff --git a/core/java/android/database/sqlite/SQLiteOpenHelper.java b/core/java/android/database/sqlite/SQLiteOpenHelper.java
index 78c8954..88d69b6 100644
--- a/core/java/android/database/sqlite/SQLiteOpenHelper.java
+++ b/core/java/android/database/sqlite/SQLiteOpenHelper.java
@@ -25,10 +25,15 @@
 import android.database.SQLException;
 import android.database.sqlite.SQLiteDatabase.CursorFactory;
 import android.os.FileUtils;
+import android.util.ArrayMap;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.io.File;
+import java.io.IOException;
 import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * A helper class to manage database creation and version management.
@@ -54,6 +59,13 @@
 public abstract class SQLiteOpenHelper implements AutoCloseable {
     private static final String TAG = SQLiteOpenHelper.class.getSimpleName();
 
+    // Every database file has a lock, saved in this map.  The lock is held while the database is
+    // opened.
+    private static final ConcurrentHashMap<String, Object> sDbLock = new ConcurrentHashMap<>();
+
+    // The lock that this open helper instance must hold when the database is opened.
+    private final Object mLock;
+
     private final Context mContext;
     @UnsupportedAppUsage
     private final String mName;
@@ -168,6 +180,21 @@
         mNewVersion = version;
         mMinimumSupportedVersion = Math.max(0, minimumSupportedVersion);
         setOpenParamsBuilder(openParamsBuilder);
+
+        Object lock = null;
+        if (mName == null || !Flags.concurrentOpenHelper()) {
+            lock = new Object();
+        } else {
+            try {
+                final String path = mContext.getDatabasePath(mName).getCanonicalPath();
+                lock = sDbLock.computeIfAbsent(path, (String k) -> new Object());
+            } catch (IOException e) {
+                Log.d(TAG, "failed to construct db path for " + mName);
+                // Ensure the lock is not null.
+                lock = new Object();
+            }
+        }
+        mLock = lock;
     }
 
     /**
@@ -358,74 +385,77 @@
 
         SQLiteDatabase db = mDatabase;
         try {
-            mIsInitializing = true;
+            synchronized (mLock) {
+                mIsInitializing = true;
 
-            if (db != null) {
-                if (writable && db.isReadOnly()) {
-                    db.reopenReadWrite();
-                }
-            } else if (mName == null) {
-                db = SQLiteDatabase.createInMemory(mOpenParamsBuilder.build());
-            } else {
-                final File filePath = mContext.getDatabasePath(mName);
-                SQLiteDatabase.OpenParams params = mOpenParamsBuilder.build();
-                try {
-                    db = SQLiteDatabase.openDatabase(filePath, params);
-                    // Keep pre-O-MR1 behavior by resetting file permissions to 660
-                    setFilePermissionsForDb(filePath.getPath());
-                } catch (SQLException ex) {
-                    if (writable) {
-                        throw ex;
+                if (db != null) {
+                    if (writable && db.isReadOnly()) {
+                        db.reopenReadWrite();
                     }
-                    Log.e(TAG, "Couldn't open database for writing (will try read-only):", ex);
-                    params = params.toBuilder().addOpenFlags(SQLiteDatabase.OPEN_READONLY).build();
-                    db = SQLiteDatabase.openDatabase(filePath, params);
-                }
-            }
-
-            onConfigure(db);
-
-            final int version = db.getVersion();
-            if (version != mNewVersion) {
-                if (db.isReadOnly()) {
-                    throw new SQLiteException("Can't upgrade read-only database from version " +
-                            db.getVersion() + " to " + mNewVersion + ": " + mName);
-                }
-
-                if (version > 0 && version < mMinimumSupportedVersion) {
-                    File databaseFile = new File(db.getPath());
-                    onBeforeDelete(db);
-                    db.close();
-                    if (SQLiteDatabase.deleteDatabase(databaseFile)) {
-                        mIsInitializing = false;
-                        return getDatabaseLocked(writable);
-                    } else {
-                        throw new IllegalStateException("Unable to delete obsolete database "
-                                + mName + " with version " + version);
-                    }
+                } else if (mName == null) {
+                    db = SQLiteDatabase.createInMemory(mOpenParamsBuilder.build());
                 } else {
-                    db.beginTransaction();
+                    final File filePath = mContext.getDatabasePath(mName);
+                    SQLiteDatabase.OpenParams params = mOpenParamsBuilder.build();
                     try {
-                        if (version == 0) {
-                            onCreate(db);
-                        } else {
-                            if (version > mNewVersion) {
-                                onDowngrade(db, version, mNewVersion);
-                            } else {
-                                onUpgrade(db, version, mNewVersion);
-                            }
+                        db = SQLiteDatabase.openDatabase(filePath, params);
+                        // Keep pre-O-MR1 behavior by resetting file permissions to 660
+                        setFilePermissionsForDb(filePath.getPath());
+                    } catch (SQLException ex) {
+                        if (writable) {
+                            throw ex;
                         }
-                        db.setVersion(mNewVersion);
-                        db.setTransactionSuccessful();
-                    } finally {
-                        db.endTransaction();
+                        Log.e(TAG, "Couldn't open database for writing (will try read-only):", ex);
+                        params = params.toBuilder()
+                                 .addOpenFlags(SQLiteDatabase.OPEN_READONLY).build();
+                        db = SQLiteDatabase.openDatabase(filePath, params);
                     }
                 }
-            }
 
-            onOpen(db);
-            mDatabase = db;
-            return db;
+                onConfigure(db);
+
+                final int version = db.getVersion();
+                if (version != mNewVersion) {
+                    if (db.isReadOnly()) {
+                        throw new SQLiteException("Can't upgrade read-only database from version "
+                                + db.getVersion() + " to " + mNewVersion + ": " + mName);
+                    }
+
+                    if (version > 0 && version < mMinimumSupportedVersion) {
+                        File databaseFile = new File(db.getPath());
+                        onBeforeDelete(db);
+                        db.close();
+                        if (SQLiteDatabase.deleteDatabase(databaseFile)) {
+                            mIsInitializing = false;
+                            return getDatabaseLocked(writable);
+                        } else {
+                            throw new IllegalStateException("Unable to delete obsolete database "
+                                    + mName + " with version " + version);
+                        }
+                    } else {
+                        db.beginTransaction();
+                        try {
+                            if (version == 0) {
+                                onCreate(db);
+                            } else {
+                                if (version > mNewVersion) {
+                                    onDowngrade(db, version, mNewVersion);
+                                } else {
+                                    onUpgrade(db, version, mNewVersion);
+                                }
+                            }
+                            db.setVersion(mNewVersion);
+                            db.setTransactionSuccessful();
+                        } finally {
+                            db.endTransaction();
+                        }
+                    }
+                }
+
+                onOpen(db);
+                mDatabase = db;
+                return db;
+            }
         } finally {
             mIsInitializing = false;
             if (db != null && db != mDatabase) {
diff --git a/core/java/android/database/sqlite/flags.aconfig b/core/java/android/database/sqlite/flags.aconfig
index d43a669..1d17a51 100644
--- a/core/java/android/database/sqlite/flags.aconfig
+++ b/core/java/android/database/sqlite/flags.aconfig
@@ -17,3 +17,12 @@
      description: "SQLite APIs held back for Android 15"
      bug: "279043253"
 }
+
+flag {
+     name: "concurrent_open_helper"
+     is_exported: true
+     namespace: "system_performance"
+     is_fixed_read_only: false
+     description: "Make SQLiteOpenHelper thread-safe"
+     bug: "335904370"
+}
diff --git a/core/java/android/hardware/contexthub/HubEndpoint.java b/core/java/android/hardware/contexthub/HubEndpoint.java
index 25cdc50..14911de 100644
--- a/core/java/android/hardware/contexthub/HubEndpoint.java
+++ b/core/java/android/hardware/contexthub/HubEndpoint.java
@@ -107,6 +107,13 @@
     @GuardedBy("mLock")
     private final SparseArray<HubEndpointSession> mActiveSessions = new SparseArray<>();
 
+    /*
+     * Internal interface used to invoke IContextHubEndpoint calls.
+     */
+    interface EndpointConsumer {
+        void accept(IContextHubEndpoint endpoint) throws RemoteException;
+    }
+
     private final IContextHubEndpointCallback mServiceCallback =
             new IContextHubEndpointCallback.Stub() {
                 @Override
@@ -115,20 +122,19 @@
                         HubEndpointInfo initiator,
                         @Nullable String serviceDescriptor)
                         throws RemoteException {
-                    HubEndpointSession activeSession;
+                    boolean sessionExists;
                     synchronized (mLock) {
-                        activeSession = mActiveSessions.get(sessionId);
+                        sessionExists = mActiveSessions.contains(sessionId);
                         // TODO(b/378974199): Consider refactor these assertions
-                        if (activeSession != null) {
-                            Log.i(
+                        if (sessionExists) {
+                            Log.w(
                                     TAG,
                                     "onSessionOpenComplete: session already exists, id="
                                             + sessionId);
-                            return;
                         }
                     }
 
-                    if (mLifecycleCallback != null) {
+                    if (!sessionExists && mLifecycleCallback != null) {
                         mLifecycleCallbackExecutor.execute(
                                 () ->
                                         processSessionOpenRequestResult(
@@ -142,96 +148,6 @@
                     }
                 }
 
-                private void processSessionOpenRequestResult(
-                        int sessionId,
-                        HubEndpointInfo initiator,
-                        @Nullable String serviceDescriptor,
-                        HubEndpointSessionResult result) {
-                    if (result == null) {
-                        throw new IllegalArgumentException(
-                                "HubEndpointSessionResult shouldn't be null.");
-                    }
-
-                    if (result.isAccepted()) {
-                        acceptSession(sessionId, initiator, serviceDescriptor);
-                    } else {
-                        Log.i(
-                                TAG,
-                                "Session "
-                                        + sessionId
-                                        + " from "
-                                        + initiator
-                                        + " was rejected, reason="
-                                        + result.getReason());
-                        rejectSession(sessionId);
-                    }
-
-                    invokeCallbackFinished();
-                }
-
-                private void acceptSession(
-                        int sessionId,
-                        HubEndpointInfo initiator,
-                        @Nullable String serviceDescriptor) {
-                    if (mServiceToken == null || mAssignedHubEndpointInfo == null) {
-                        // No longer registered?
-                        return;
-                    }
-
-                    // Retrieve the active session
-                    HubEndpointSession activeSession;
-                    synchronized (mLock) {
-                        activeSession = mActiveSessions.get(sessionId);
-                        // TODO(b/378974199): Consider refactor these assertions
-                        if (activeSession != null) {
-                            Log.e(
-                                    TAG,
-                                    "onSessionOpenRequestResult: session already exists, id="
-                                            + sessionId);
-                            return;
-                        }
-
-                        activeSession =
-                                new HubEndpointSession(
-                                        sessionId,
-                                        HubEndpoint.this,
-                                        mAssignedHubEndpointInfo,
-                                        initiator,
-                                        serviceDescriptor);
-                        try {
-                            // oneway call to notify system service that the request is completed
-                            mServiceToken.openSessionRequestComplete(sessionId);
-                        } catch (RemoteException e) {
-                            Log.e(TAG, "onSessionOpenRequestResult: ", e);
-                            return;
-                        }
-
-                        mActiveSessions.put(sessionId, activeSession);
-                    }
-
-                    // Execute the callback
-                    activeSession.setOpened();
-                    if (mLifecycleCallback != null) {
-                        final HubEndpointSession finalActiveSession = activeSession;
-                        mLifecycleCallbackExecutor.execute(
-                                () -> mLifecycleCallback.onSessionOpened(finalActiveSession));
-                    }
-                }
-
-                private void rejectSession(int sessionId) {
-                    if (mServiceToken == null || mAssignedHubEndpointInfo == null) {
-                        // No longer registered?
-                        return;
-                    }
-
-                    try {
-                        mServiceToken.closeSession(
-                                sessionId, REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED);
-                    } catch (RemoteException e) {
-                        e.rethrowFromSystemServer();
-                    }
-                }
-
                 @Override
                 public void onSessionOpenComplete(int sessionId) throws RemoteException {
                     final HubEndpointSession activeSession;
@@ -242,16 +158,15 @@
                     }
                     // TODO(b/378974199): Consider refactor these assertions
                     if (activeSession == null) {
-                        Log.i(
+                        Log.w(
                                 TAG,
                                 "onSessionOpenComplete: no pending session open request? id="
                                         + sessionId);
-                        return;
+                    } else {
+                        activeSession.setOpened();
                     }
 
-                    // Execute the callback
-                    activeSession.setOpened();
-                    if (mLifecycleCallback != null) {
+                    if (activeSession != null && mLifecycleCallback != null) {
                         mLifecycleCallbackExecutor.execute(
                                 () -> {
                                     mLifecycleCallback.onSessionOpened(activeSession);
@@ -272,12 +187,11 @@
                     }
                     // TODO(b/378974199): Consider refactor these assertions
                     if (activeSession == null) {
-                        Log.i(TAG, "onSessionClosed: session not active, id=" + sessionId);
-                        return;
+                        Log.w(TAG, "onSessionClosed: session not active, id=" + sessionId);
                     }
 
                     // Execute the callback
-                    if (mLifecycleCallback != null) {
+                    if (activeSession != null && mLifecycleCallback != null) {
                         mLifecycleCallbackExecutor.execute(
                                 () -> {
                                     mLifecycleCallback.onSessionClosed(activeSession, reason);
@@ -304,44 +218,120 @@
                         activeSession = mActiveSessions.get(sessionId);
                     }
                     if (activeSession == null) {
-                        Log.i(TAG, "onMessageReceived: session not active, id=" + sessionId);
+                        Log.w(TAG, "onMessageReceived: session not active, id=" + sessionId);
                     }
 
                     if (activeSession == null || mMessageCallback == null) {
-                        if (message.isResponseRequired()) {
-                            try {
-                                mServiceToken.sendMessageDeliveryStatus(
-                                        sessionId,
-                                        message.getMessageSequenceNumber(),
-                                        ErrorCode.DESTINATION_NOT_FOUND);
-                            } catch (RemoteException e) {
-                                e.rethrowFromSystemServer();
-                            }
+                        sendMessageDeliveryStatus(
+                                sessionId, message, ErrorCode.DESTINATION_NOT_FOUND);
+                    } else {
+                        mMessageCallbackExecutor.execute(
+                                () -> {
+                                    mMessageCallback.onMessageReceived(activeSession, message);
+                                    sendMessageDeliveryStatus(sessionId, message, ErrorCode.OK);
+                                });
+                    }
+                }
+
+                private void sendMessageDeliveryStatus(
+                        int sessionId, HubMessage message, byte errorCode) {
+                    if (message.isResponseRequired()) {
+                        invokeCallback(
+                                (callback) ->
+                                        callback.sendMessageDeliveryStatus(
+                                                sessionId,
+                                                message.getMessageSequenceNumber(),
+                                                errorCode));
+                    }
+                    invokeCallbackFinished();
+                }
+
+                private void processSessionOpenRequestResult(
+                        int sessionId,
+                        HubEndpointInfo initiator,
+                        @Nullable String serviceDescriptor,
+                        HubEndpointSessionResult result) {
+                    if (result == null) {
+                        throw new IllegalArgumentException(
+                                "HubEndpointSessionResult shouldn't be null.");
+                    }
+
+                    if (result.isAccepted()) {
+                        acceptSession(sessionId, initiator, serviceDescriptor);
+                    } else {
+                        Log.e(
+                                TAG,
+                                "Session "
+                                        + sessionId
+                                        + " from "
+                                        + initiator
+                                        + " was rejected, reason="
+                                        + result.getReason());
+                        rejectSession(sessionId);
+                    }
+
+                    invokeCallbackFinished();
+                }
+
+                private void acceptSession(
+                        int sessionId,
+                        HubEndpointInfo initiator,
+                        @Nullable String serviceDescriptor) {
+                    // Retrieve the active session
+                    HubEndpointSession activeSession;
+                    synchronized (mLock) {
+                        activeSession = mActiveSessions.get(sessionId);
+                        // TODO(b/378974199): Consider refactor these assertions
+                        if (activeSession != null) {
+                            Log.e(
+                                    TAG,
+                                    "onSessionOpenRequestResult: session already exists, id="
+                                            + sessionId);
+                            return;
                         }
-                        return;
+
+                        activeSession =
+                                new HubEndpointSession(
+                                        sessionId,
+                                        HubEndpoint.this,
+                                        mAssignedHubEndpointInfo,
+                                        initiator,
+                                        serviceDescriptor);
+
+                        invokeCallback(
+                                (callback) -> callback.openSessionRequestComplete(sessionId));
+                        mActiveSessions.put(sessionId, activeSession);
                     }
 
                     // Execute the callback
-                    mMessageCallbackExecutor.execute(
-                            () -> {
-                                mMessageCallback.onMessageReceived(activeSession, message);
-                                if (message.isResponseRequired()) {
-                                    try {
-                                        mServiceToken.sendMessageDeliveryStatus(
-                                                sessionId,
-                                                message.getMessageSequenceNumber(),
-                                                ErrorCode.OK);
-                                    } catch (RemoteException e) {
-                                        e.rethrowFromSystemServer();
-                                    }
-                                }
-                                invokeCallbackFinished();
-                            });
+                    activeSession.setOpened();
+                    if (mLifecycleCallback != null) {
+                        final HubEndpointSession finalActiveSession = activeSession;
+                        mLifecycleCallbackExecutor.execute(
+                                () -> mLifecycleCallback.onSessionOpened(finalActiveSession));
+                    }
+                }
+
+                private void rejectSession(int sessionId) {
+                    invokeCallback(
+                            (callback) ->
+                                    callback.closeSession(
+                                            sessionId,
+                                            REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED));
                 }
 
                 private void invokeCallbackFinished() {
+                    invokeCallback((callback) -> callback.onCallbackFinished());
+                }
+
+                private void invokeCallback(EndpointConsumer consumer) {
                     try {
-                        mServiceToken.onCallbackFinished();
+                        consumer.accept(mServiceToken);
+                    } catch (IllegalStateException e) {
+                        // It's possible to hit this exception if the endpoint was unregistered
+                        // while processing the callback. It's not a fatal error so we just log
+                        // a warning.
+                        Log.w(TAG, "IllegalStateException while calling callback", e);
                     } catch (RemoteException e) {
                         e.rethrowFromSystemServer();
                     }
@@ -369,11 +359,6 @@
 
     /** @hide */
     public void register(IContextHubService service) {
-        // TODO(b/378974199): Consider refactor these assertions
-        if (mServiceToken != null) {
-            // Already registered
-            return;
-        }
         try {
             IContextHubEndpoint serviceToken =
                     service.registerEndpoint(
@@ -391,13 +376,6 @@
 
     /** @hide */
     public void unregister() {
-        IContextHubEndpoint serviceToken = mServiceToken;
-        // TODO(b/378974199): Consider refactor these assertions
-        if (serviceToken == null) {
-            // Not yet registered
-            return;
-        }
-
         try {
             synchronized (mLock) {
                 // Don't call HubEndpointSession.close() here.
@@ -410,20 +388,11 @@
         } catch (RemoteException e) {
             Log.e(TAG, "unregisterEndpoint: failed to unregister endpoint", e);
             e.rethrowFromSystemServer();
-        } finally {
-            mServiceToken = null;
-            mAssignedHubEndpointInfo = null;
         }
     }
 
     /** @hide */
     public void openSession(HubEndpointInfo destinationInfo, @Nullable String serviceDescriptor) {
-        // TODO(b/378974199): Consider refactor these assertions
-        if (mServiceToken == null || mAssignedHubEndpointInfo == null) {
-            // No longer registered?
-            return;
-        }
-
         HubEndpointSession newSession;
         try {
             synchronized (mLock) {
@@ -449,13 +418,6 @@
 
     /** @hide */
     public void closeSession(HubEndpointSession session) {
-        IContextHubEndpoint serviceToken = mServiceToken;
-        // TODO(b/378974199): Consider refactor these assertions
-        if (serviceToken == null || mAssignedHubEndpointInfo == null) {
-            // Not registered
-            return;
-        }
-
         synchronized (mLock) {
             if (!mActiveSessions.contains(session.getId())) {
                 // Already closed?
@@ -466,8 +428,7 @@
         }
 
         try {
-            // Oneway notification to system service
-            serviceToken.closeSession(session.getId(), REASON_CLOSE_ENDPOINT_SESSION_REQUESTED);
+            mServiceToken.closeSession(session.getId(), REASON_CLOSE_ENDPOINT_SESSION_REQUESTED);
         } catch (RemoteException e) {
             Log.e(TAG, "closeSession: failed to close session " + session, e);
             e.rethrowFromSystemServer();
@@ -478,14 +439,8 @@
             HubEndpointSession session,
             HubMessage message,
             @Nullable IContextHubTransactionCallback transactionCallback) {
-        IContextHubEndpoint serviceToken = mServiceToken;
-        if (serviceToken == null) {
-            // Not registered
-            return;
-        }
-
         try {
-            serviceToken.sendMessage(session.getId(), message, transactionCallback);
+            mServiceToken.sendMessage(session.getId(), message, transactionCallback);
         } catch (RemoteException e) {
             Log.e(TAG, "sendMessage: failed to send message session=" + session, e);
             e.rethrowFromSystemServer();
diff --git a/core/java/android/hardware/devicestate/feature/flags.aconfig b/core/java/android/hardware/devicestate/feature/flags.aconfig
index 6230f4d..44d662f 100644
--- a/core/java/android/hardware/devicestate/feature/flags.aconfig
+++ b/core/java/android/hardware/devicestate/feature/flags.aconfig
@@ -38,4 +38,16 @@
     description: "Enables Rear Display Mode V2, where the inner display shows the user a UI affordance for exiting the state"
     bug: "372486634"
     is_fixed_read_only: true
-}
\ No newline at end of file
+}
+
+flag {
+    name: "device_state_configuration_flag"
+    is_exported: true
+    namespace: "windowing_sdk"
+    description: "Re-add flag parsing for device_state_configuration.xml configuration for devices that didn't update vendor images."
+    bug: "388366842"
+    is_fixed_read_only: true
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java
index 7257055..2a9ee7f 100644
--- a/core/java/android/hardware/display/VirtualDisplayConfig.java
+++ b/core/java/android/hardware/display/VirtualDisplayConfig.java
@@ -237,10 +237,9 @@
      * @see Builder#setHomeSupported
      * @hide
      */
-    @FlaggedApi(android.companion.virtual.flags.Flags.FLAG_VDM_CUSTOM_HOME)
     @SystemApi
     public boolean isHomeSupported() {
-        return android.companion.virtual.flags.Flags.vdmCustomHome() && mIsHomeSupported;
+        return mIsHomeSupported;
     }
 
     /**
@@ -605,7 +604,6 @@
          * @see DisplayManager#VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
          * @hide
          */
-        @FlaggedApi(android.companion.virtual.flags.Flags.FLAG_VDM_CUSTOM_HOME)
         @SystemApi
         @NonNull
         public Builder setHomeSupported(boolean isHomeSupported) {
diff --git a/core/java/android/hardware/input/VirtualStylus.java b/core/java/android/hardware/input/VirtualStylus.java
index 4b79bc4..32aac2e 100644
--- a/core/java/android/hardware/input/VirtualStylus.java
+++ b/core/java/android/hardware/input/VirtualStylus.java
@@ -16,11 +16,9 @@
 
 package android.hardware.input;
 
-import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.companion.virtual.IVirtualDevice;
-import android.companion.virtual.flags.Flags;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
@@ -34,7 +32,6 @@
  *
  * @hide
  */
-@FlaggedApi(Flags.FLAG_VIRTUAL_STYLUS)
 @SystemApi
 public class VirtualStylus extends VirtualInputDevice {
     /** @hide */
diff --git a/core/java/android/hardware/input/VirtualStylusButtonEvent.java b/core/java/android/hardware/input/VirtualStylusButtonEvent.java
index 8fcf561b..9fe725a 100644
--- a/core/java/android/hardware/input/VirtualStylusButtonEvent.java
+++ b/core/java/android/hardware/input/VirtualStylusButtonEvent.java
@@ -16,11 +16,9 @@
 
 package android.hardware.input;
 
-import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
-import android.companion.virtual.flags.Flags;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.SystemClock;
@@ -35,7 +33,6 @@
  *
  * @hide
  */
-@FlaggedApi(Flags.FLAG_VIRTUAL_STYLUS)
 @SystemApi
 public final class VirtualStylusButtonEvent implements Parcelable {
     /** @hide */
@@ -128,7 +125,6 @@
     /**
      * Builder for {@link VirtualStylusButtonEvent}.
      */
-    @FlaggedApi(Flags.FLAG_VIRTUAL_STYLUS)
     public static final class Builder {
 
         @Action
diff --git a/core/java/android/hardware/input/VirtualStylusConfig.java b/core/java/android/hardware/input/VirtualStylusConfig.java
index 64cf1f5..3c56023f 100644
--- a/core/java/android/hardware/input/VirtualStylusConfig.java
+++ b/core/java/android/hardware/input/VirtualStylusConfig.java
@@ -16,11 +16,9 @@
 
 package android.hardware.input;
 
-import android.annotation.FlaggedApi;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
-import android.companion.virtual.flags.Flags;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -29,7 +27,6 @@
  *
  * @hide
  */
-@FlaggedApi(Flags.FLAG_VIRTUAL_STYLUS)
 @SystemApi
 public final class VirtualStylusConfig extends VirtualTouchDeviceConfig implements Parcelable {
 
@@ -68,7 +65,6 @@
     /**
      * Builder for creating a {@link VirtualStylusConfig}.
      */
-    @FlaggedApi(Flags.FLAG_VIRTUAL_STYLUS)
     public static final class Builder extends VirtualTouchDeviceConfig.Builder<Builder> {
 
         /**
diff --git a/core/java/android/hardware/input/VirtualStylusMotionEvent.java b/core/java/android/hardware/input/VirtualStylusMotionEvent.java
index 0ac6f3a..fa0ff4f 100644
--- a/core/java/android/hardware/input/VirtualStylusMotionEvent.java
+++ b/core/java/android/hardware/input/VirtualStylusMotionEvent.java
@@ -16,12 +16,10 @@
 
 package android.hardware.input;
 
-import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
-import android.companion.virtual.flags.Flags;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.SystemClock;
@@ -38,7 +36,6 @@
  *
  * @hide
  */
-@FlaggedApi(Flags.FLAG_VIRTUAL_STYLUS)
 @SystemApi
 public final class VirtualStylusMotionEvent implements Parcelable {
     private static final int TILT_MIN = -90;
@@ -209,7 +206,6 @@
     /**
      * Builder for {@link VirtualStylusMotionEvent}.
      */
-    @FlaggedApi(Flags.FLAG_VIRTUAL_STYLUS)
     public static final class Builder {
 
         @ToolType
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 7887c15..6212696 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -178,6 +178,13 @@
 }
 
 flag {
+    name: "enable_display_color_inversion_key_gestures"
+    namespace: "input"
+    description: "Adds key gestures for display color inversion for accessibility needs"
+    bug: "383730505"
+}
+
+flag {
     name: "enable_talkback_and_magnifier_key_gestures"
     namespace: "input"
     description: "Adds key gestures for talkback and magnifier"
diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
index d9969d8..3026609 100644
--- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java
+++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
@@ -36,7 +36,6 @@
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.ravenwood.RavenwoodEnvironment;
 
 import dalvik.annotation.optimization.NeverCompile;
@@ -1392,12 +1391,12 @@
                 } while (msg != null && !msg.isAsynchronous());
             }
             if (msg != null) {
+                if (peek) {
+                    return msg;
+                }
                 if (now >= msg.when) {
                     // Got a message.
                     mBlocked = false;
-                    if (peek) {
-                        return msg;
-                    }
                     if (prevMsg != null) {
                         prevMsg.next = msg.next;
                         if (prevMsg.next == null) {
diff --git a/core/java/android/os/IpcDataCache.java b/core/java/android/os/IpcDataCache.java
index a2e9314..240bc4f 100644
--- a/core/java/android/os/IpcDataCache.java
+++ b/core/java/android/os/IpcDataCache.java
@@ -658,4 +658,74 @@
             }
         });
     }
+
+    /**
+     * The following APIs are exposed to support testing.  They only forward the superclass but
+     * that means the superclass does not have to expose the APIs itself.
+     */
+
+    /**
+     * Stop disabling local caches with the same name as <this>.  Any caches that are currently
+     * disabled remain disabled (the "disabled" setting is sticky).  However, new caches with this
+     * name will not be disabled.  It is not an error if the cache name is not found in the list
+     * of disabled caches.
+     * @hide
+     */
+    @TestApi
+    @Override
+    public final void forgetDisableLocal() {
+        super.forgetDisableLocal();
+    }
+
+    /**
+     * Return whether a cache instance is disabled.
+     * @hide
+     */
+    @TestApi
+    @Override
+    public final boolean isDisabled() {
+        return super.isDisabled();
+    }
+
+    /**
+     * This is an obsolete synonym for {@link #isDisabled()}.
+     * @hide
+     */
+    @TestApi
+    public boolean getDisabledState() {
+        return isDisabled();
+    }
+
+    /**
+     * Disable the use of this cache in this process.  This method is used internally and during
+     * testing.  To disable a cache in normal code, use disableProcessLocal().
+     * @hide
+     */
+    @TestApi
+    @Override
+    public final void disableInstance() {
+        super.disableInstance();
+    }
+
+    /**
+     * Disable all caches that use the property as the current cache.
+     * @hide
+     */
+    @TestApi
+    @Override
+    public final void disableSystemWide() {
+        super.disableSystemWide();
+    }
+
+    /**
+     * Enable or disable testing.  The protocol requires that the mode toggle: for instance, it is
+     * illegal to clear the test mode if the test mode is already off.  The purpose is solely to
+     * ensure that test clients do not forget to use the test mode properly, even though the
+     * current logic does not care.
+     * @hide
+     */
+    @TestApi
+    public static void setTestMode(boolean mode) {
+        PropertyInvalidatedCache.setTestMode(mode);
+    }
 }
diff --git a/core/java/android/os/LegacyMessageQueue/MessageQueue.java b/core/java/android/os/LegacyMessageQueue/MessageQueue.java
index c0333e9..d12d99a 100644
--- a/core/java/android/os/LegacyMessageQueue/MessageQueue.java
+++ b/core/java/android/os/LegacyMessageQueue/MessageQueue.java
@@ -16,7 +16,6 @@
 
 package android.os;
 
-import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -754,12 +753,12 @@
                 } while (msg != null && !msg.isAsynchronous());
             }
             if (msg != null) {
+                if (peek) {
+                    return msg;
+                }
                 if (now >= msg.when) {
                     // Got a message.
                     mBlocked = false;
-                    if (peek) {
-                        return msg;
-                    }
                     if (prevMsg != null) {
                         prevMsg.next = msg.next;
                         if (prevMsg.next == null) {
diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java
index 0125905..2fe4871 100644
--- a/core/java/android/os/Looper.java
+++ b/core/java/android/os/Looper.java
@@ -70,6 +70,13 @@
 
     private static final String TAG = "Looper";
 
+    private static class NoImagePreloadHolder {
+        // Enable/Disable verbose logging with a system prop. e.g.
+        // adb shell 'setprop log.looper.slow.verbose false && stop && start'
+        private static final boolean sVerboseLogging =
+                SystemProperties.getBoolean("log.looper.slow.verbose", false);
+    }
+
     // sThreadLocal.get() will return null unless you've called prepare().
     @UnsupportedAppUsage
     static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
@@ -246,17 +253,21 @@
             }
         }
         if (logSlowDelivery) {
+            boolean slow = false;
+
+            if (!me.mSlowDeliveryDetected || NoImagePreloadHolder.sVerboseLogging) {
+                slow = showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart,
+                        "delivery", msg);
+            }
             if (me.mSlowDeliveryDetected) {
-                if ((dispatchStart - msg.when) <= 10) {
+                if (!slow && (dispatchStart - msg.when) <= 10) {
                     Slog.w(TAG, "Drained");
                     me.mSlowDeliveryDetected = false;
                 }
-            } else {
-                if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
-                        msg)) {
-                    // Once we write a slow delivery log, suppress until the queue drains.
-                    me.mSlowDeliveryDetected = true;
-                }
+            } else if (slow) {
+                // A slow delivery is detected, suppressing further logs unless verbose logging
+                // is enabled.
+                me.mSlowDeliveryDetected = true;
             }
         }
         if (logSlowDispatch) {
@@ -322,6 +333,23 @@
 
     @android.ravenwood.annotation.RavenwoodReplace
     private static int getThresholdOverride() {
+        // Allow overriding the threshold for all processes' main looper with a system prop.
+        // e.g. adb shell 'setprop log.looper.any.main.slow 1 && stop && start'
+        if (myLooper() == getMainLooper()) {
+            final int globalOverride = SystemProperties.getInt("log.looper.any.main.slow", -1);
+            if (globalOverride >= 0) {
+                return globalOverride;
+            }
+        }
+
+        // Allow overriding the threshold for all threads within a process with a system prop.
+        // e.g. adb shell 'setprop log.looper.1000.any.slow 1 && stop && start'
+        final int processOverride = SystemProperties.getInt("log.looper."
+                + Process.myUid() + ".any.slow", -1);
+        if (processOverride >= 0) {
+            return processOverride;
+        }
+
         return SystemProperties.getInt("log.looper."
                 + Process.myUid() + "."
                 + Thread.currentThread().getName()
diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING
index effe555..48fa0c8 100644
--- a/core/java/android/os/TEST_MAPPING
+++ b/core/java/android/os/TEST_MAPPING
@@ -112,6 +112,32 @@
     {
       "file_patterns": ["Bugreport[^/]*\\.java"],
       "name": "ShellTests"
+    },
+    {
+      "file_patterns": [
+        "CpuHeadroom[^/]*",
+        "GpuHeadroom[^/]*",
+        "health/SystemHealthManager\\.java"
+      ],
+      "name": "CtsOsTestCases",
+      "options": [
+        {"include-filter": "android.os.health.cts.HeadroomTest"},
+        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+        {"exclude-annotation": "org.junit.Ignore"}
+      ]
+    },
+    {
+      "file_patterns": [
+        "CpuHeadroom[^/]*",
+        "GpuHeadroom[^/]*",
+        "health/SystemHealthManager\\.java"
+      ],
+      "name": "FrameworksCoreTests",
+      "options": [
+        {"include-filter": "android.os.SystemHealthManagerUnitTest"},
+        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+        {"exclude-annotation": "org.junit.Ignore"}
+      ]
     }
   ],
   "ravenwood-presubmit": [
diff --git a/core/java/android/os/health/SystemHealthManager.java b/core/java/android/os/health/SystemHealthManager.java
index febbfca..9d0e221 100644
--- a/core/java/android/os/health/SystemHealthManager.java
+++ b/core/java/android/os/health/SystemHealthManager.java
@@ -344,11 +344,7 @@
                 || !mHintManagerClientData.supportInfo.headroom.isCpuSupported) {
             throw new UnsupportedOperationException();
         }
-        try {
-            return mHintManager.getCpuHeadroomMinIntervalMillis();
-        } catch (RemoteException re) {
-            throw re.rethrowFromSystemServer();
-        }
+        return mHintManagerClientData.supportInfo.headroom.cpuMinIntervalMillis;
     }
 
     /**
@@ -366,11 +362,7 @@
                 || !mHintManagerClientData.supportInfo.headroom.isGpuSupported) {
             throw new UnsupportedOperationException();
         }
-        try {
-            return mHintManager.getGpuHeadroomMinIntervalMillis();
-        } catch (RemoteException re) {
-            throw re.rethrowFromSystemServer();
-        }
+        return mHintManagerClientData.supportInfo.headroom.gpuMinIntervalMillis;
     }
 
     /**
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index baaaa46..0cfec2c 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1254,21 +1254,6 @@
             "android.settings.TEMPERATURE_UNIT_SETTINGS";
 
     /**
-     * Activity Action: Show numbering system configuration settings.
-     * <p>
-     * Input: Nothing.
-     * <p>
-     * Output: After calling {@link android.app.Activity#startActivityForResult}, the callback
-     * {@code onActivityResult} will have resultCode {@link android.app.Activity#RESULT_OK} if
-     * the numbering system settings page is suitable to show on the UI. Otherwise, the result is
-     * set to {@link android.app.Activity#RESULT_CANCELED}.
-     */
-    @FlaggedApi(Flags.FLAG_SYSTEM_REGIONAL_PREFERENCES_API_ENABLED)
-    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
-    public static final String ACTION_NUMBERING_SYSTEM_SETTINGS =
-            "android.settings.NUMBERING_SYSTEM_SETTINGS";
-
-    /**
      * Activity Action: Show measurement system configuration settings.
      * <p>
      * Input: Nothing.
diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java
index 146c2b6..105fa3f 100644
--- a/core/java/android/service/notification/StatusBarNotification.java
+++ b/core/java/android/service/notification/StatusBarNotification.java
@@ -30,13 +30,17 @@
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.Trace;
 import android.os.UserHandle;
+import android.util.ArrayMap;
 
 import com.android.internal.logging.InstanceId;
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import static com.android.window.flags.Flags.enablePerDisplayPackageContextCacheInStatusbarNotif;
 
 import java.util.ArrayList;
+import java.util.Map;
 
 /**
  * Class encapsulating a Notification. Sent by the NotificationManagerService to clients including
@@ -69,7 +73,15 @@
     // A small per-notification ID, used for statsd logging.
     private InstanceId mInstanceId;  // Not final, see setInstanceId()
 
+    /**
+     * @deprecated This field is only used when
+     * {@link enablePerDisplayPackageContextCacheInStatusbarNotif}
+     * is disabled.
+     */
+    @Deprecated
     private Context mContext; // used for inflation & icon expansion
+    // Maps display id to context used for remote view content inflation and status bar icon.
+    private final Map<Integer, Context> mContextForDisplayId = new ArrayMap<>();
 
     /** @hide */
     public StatusBarNotification(String pkg, String opPkg, int id,
@@ -453,7 +465,11 @@
      * @hide
      */
     public void clearPackageContext() {
-        mContext = null;
+        if (enablePerDisplayPackageContextCacheInStatusbarNotif()) {
+            mContextForDisplayId.clear();
+        } else {
+            mContext = null;
+        }
     }
 
     /**
@@ -475,21 +491,42 @@
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public Context getPackageContext(Context context) {
-        if (mContext == null) {
-            try {
-                ApplicationInfo ai = context.getPackageManager()
-                        .getApplicationInfoAsUser(pkg, PackageManager.MATCH_UNINSTALLED_PACKAGES,
-                                getNormalizedUserId());
-                mContext = context.createApplicationContext(ai,
-                        Context.CONTEXT_RESTRICTED);
-            } catch (PackageManager.NameNotFoundException e) {
-                mContext = null;
+        if (enablePerDisplayPackageContextCacheInStatusbarNotif()) {
+            if (context == null) return null;
+            return mContextForDisplayId.computeIfAbsent(context.getDisplayId(),
+                    (displayId) -> createPackageContext(context));
+        } else {
+            if (mContext == null) {
+                try {
+                    ApplicationInfo ai = context.getPackageManager()
+                            .getApplicationInfoAsUser(pkg,
+                                    PackageManager.MATCH_UNINSTALLED_PACKAGES,
+                                    getNormalizedUserId());
+                    mContext = context.createApplicationContext(ai,
+                            Context.CONTEXT_RESTRICTED);
+                } catch (PackageManager.NameNotFoundException e) {
+                    mContext = null;
+                }
             }
+            if (mContext == null) {
+                mContext = context;
+            }
+            return mContext;
         }
-        if (mContext == null) {
-            mContext = context;
+    }
+
+    private Context createPackageContext(Context context) {
+        try {
+            Trace.beginSection("StatusBarNotification#createPackageContext");
+            ApplicationInfo ai = context.getPackageManager()
+                    .getApplicationInfoAsUser(pkg, PackageManager.MATCH_UNINSTALLED_PACKAGES,
+                            getNormalizedUserId());
+            return context.createApplicationContext(ai, Context.CONTEXT_RESTRICTED);
+        } catch (PackageManager.NameNotFoundException e) {
+            return context;
+        } finally {
+            Trace.endSection();
         }
-        return mContext;
     }
 
     /**
diff --git a/core/java/android/service/settings/preferences/SettingsPreferenceValue.java b/core/java/android/service/settings/preferences/SettingsPreferenceValue.java
index eea93b3..2e661b4 100644
--- a/core/java/android/service/settings/preferences/SettingsPreferenceValue.java
+++ b/core/java/android/service/settings/preferences/SettingsPreferenceValue.java
@@ -34,8 +34,7 @@
  * This objects represents a value that can be used for a particular settings preference.
  * <p>The data type for the value will correspond to {@link #getType}. For possible types, see
  * constants below, such as {@link #TYPE_BOOLEAN} and {@link #TYPE_STRING}.
- * Depending on the type, the corresponding getter will contain its value. All other getters will
- * return default values (boolean returns false, String returns null) so they should not be used.
+ * Depending on the type, the corresponding getter will contain its value.
  * <p>See documentation on the constants for which getter method should be used.
  */
 @FlaggedApi(Flags.FLAG_SETTINGS_CATALYST)
@@ -43,12 +42,7 @@
 
     @Type
     private final int mType;
-    private final boolean mBooleanValue;
-    private final int mIntValue;
-    private final long mLongValue;
-    private final double mDoubleValue;
-    @Nullable
-    private final String mStringValue;
+    private final @Nullable Object mValue;
 
     /**
      * Returns the type indicator for Preference value.
@@ -59,39 +53,39 @@
     }
 
     /**
-     * Returns the boolean value for Preference if type is {@link #TYPE_BOOLEAN}.
+     * Returns the boolean value for Preference, the type must be {@link #TYPE_BOOLEAN}.
      */
     public boolean getBooleanValue() {
-        return mBooleanValue;
+        return (boolean) mValue;
     }
 
     /**
-     * Returns the int value for Preference if type is {@link #TYPE_INT}.
+     * Returns the int value for Preference, the type must be {@link #TYPE_INT}.
      */
     public int getIntValue() {
-        return mIntValue;
+        return (int) mValue;
     }
 
     /**
-     * Returns the long value for Preference if type is {@link #TYPE_LONG}.
+     * Returns the long value for Preference, the type must be {@link #TYPE_LONG}.
      */
     public long getLongValue() {
-        return mLongValue;
+        return (long) mValue;
     }
 
     /**
-     * Returns the double value for Preference if type is {@link #TYPE_DOUBLE}.
+     * Returns the double value for Preference, the type must be {@link #TYPE_DOUBLE}.
      */
     public double getDoubleValue() {
-        return mDoubleValue;
+        return (double) mValue;
     }
 
     /**
-     * Returns the string value for Preference if type is {@link #TYPE_STRING}.
+     * Returns the string value for Preference, the type must be {@link #TYPE_STRING}.
      */
     @Nullable
     public String getStringValue() {
-        return mStringValue;
+        return (String) mValue;
     }
 
     /** @hide */
@@ -115,34 +109,47 @@
     public static final int TYPE_STRING = 3;
     /** Value is of type int. Access via {@link #getIntValue}. */
     public static final int TYPE_INT = 4;
+    /** Max type value. */
+    private static final int MAX_TYPE_VALUE = TYPE_INT;
 
     private SettingsPreferenceValue(@NonNull Builder builder) {
         mType = builder.mType;
-        mBooleanValue = builder.mBooleanValue;
-        mLongValue = builder.mLongValue;
-        mDoubleValue = builder.mDoubleValue;
-        mStringValue = builder.mStringValue;
-        mIntValue = builder.mIntValue;
+        mValue = builder.mValue;
     }
 
     private SettingsPreferenceValue(@NonNull Parcel in) {
         mType = in.readInt();
-        mBooleanValue = in.readBoolean();
-        mLongValue = in.readLong();
-        mDoubleValue = in.readDouble();
-        mStringValue = in.readString8();
-        mIntValue = in.readInt();
+        if (mType == TYPE_BOOLEAN) {
+            mValue = in.readBoolean();
+        } else if (mType == TYPE_LONG) {
+            mValue = in.readLong();
+        } else if (mType == TYPE_DOUBLE) {
+            mValue = in.readDouble();
+        } else if (mType == TYPE_STRING) {
+            mValue = in.readString();
+        } else if (mType == TYPE_INT) {
+            mValue = in.readInt();
+        } else {
+            // throw exception immediately, further read to Parcel may be invalid
+            throw new IllegalStateException("Unknown type: " + mType);
+        }
     }
 
     /** @hide */
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeInt(mType);
-        dest.writeBoolean(mBooleanValue);
-        dest.writeLong(mLongValue);
-        dest.writeDouble(mDoubleValue);
-        dest.writeString8(mStringValue);
-        dest.writeInt(mIntValue);
+        if (mType == TYPE_BOOLEAN) {
+            dest.writeBoolean(getBooleanValue());
+        } else if (mType == TYPE_LONG) {
+            dest.writeLong(getLongValue());
+        } else if (mType == TYPE_DOUBLE) {
+            dest.writeDouble(getDoubleValue());
+        } else if (mType == TYPE_STRING) {
+            dest.writeString(getStringValue());
+        } else if (mType == TYPE_INT) {
+            dest.writeInt(getIntValue());
+        }
     }
 
     /** @hide */
@@ -174,17 +181,16 @@
     public static final class Builder {
         @Type
         private final int mType;
-        private boolean mBooleanValue;
-        private long mLongValue;
-        private double mDoubleValue;
-        private String mStringValue;
-        private int mIntValue;
+        private @Nullable Object mValue;
 
         /**
          * Create Builder instance.
          * @param type type indicator for preference value
          */
         public Builder(@Type int type) {
+            if (type < 0 || type > MAX_TYPE_VALUE) {
+                throw new IllegalArgumentException("Unknown type: " + type);
+            }
             mType = type;
         }
 
@@ -194,7 +200,8 @@
         @SuppressLint("MissingGetterMatchingBuilder")
         @NonNull
         public Builder setBooleanValue(boolean booleanValue) {
-            mBooleanValue = booleanValue;
+            checkType(TYPE_BOOLEAN);
+            mValue = booleanValue;
             return this;
         }
 
@@ -203,7 +210,8 @@
          */
         @NonNull
         public Builder setIntValue(int intValue) {
-            mIntValue = intValue;
+            checkType(TYPE_INT);
+            mValue = intValue;
             return this;
         }
 
@@ -212,7 +220,8 @@
          */
         @NonNull
         public Builder setLongValue(long longValue) {
-            mLongValue = longValue;
+            checkType(TYPE_LONG);
+            mValue = longValue;
             return this;
         }
 
@@ -221,7 +230,8 @@
          */
         @NonNull
         public Builder setDoubleValue(double doubleValue) {
-            mDoubleValue = doubleValue;
+            checkType(TYPE_DOUBLE);
+            mValue = doubleValue;
             return this;
         }
 
@@ -230,10 +240,17 @@
          */
         @NonNull
         public Builder setStringValue(@Nullable String stringValue) {
-            mStringValue = stringValue;
+            checkType(TYPE_STRING);
+            mValue = stringValue;
             return this;
         }
 
+        private void checkType(int type) {
+            if (mType != type) {
+                throw new IllegalArgumentException("Type is: " + mType);
+            }
+        }
+
         /**
          * Constructs an immutable {@link SettingsPreferenceValue} object.
          */
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 3c53506..323d83b 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -1066,6 +1066,12 @@
                         var hasBgColorChanged = newBackground != bgPaint.getColor();
 
                         if (lineNum != mLastLineNum || hasBgColorChanged) {
+                            // Skip processing if the character is a space or a tap to avoid
+                            // rendering an abrupt, empty rectangle.
+                            if (Character.isWhitespace(mText.charAt(index))) {
+                                return;
+                            }
+
                             // Draw what we have so far, then reset the rect and update its color
                             drawRect();
                             mLineBackground.set(left, top, right, bottom);
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 4fead2a..6decd6d 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -112,6 +112,7 @@
     private Insets mPendingInsets;
     private float mPendingFraction;
     private boolean mFinished;
+    private boolean mCancelling;
     private boolean mCancelled;
     private boolean mShownOnFinish;
     private float mCurrentAlpha = 1.0f;
@@ -371,7 +372,7 @@
         mPendingInsets = mLayoutInsetsDuringAnimation == LAYOUT_INSETS_DURING_ANIMATION_SHOWN
                 ? mShownInsets : mHiddenInsets;
         mPendingAlpha = 1f;
-        mPendingFraction = 1f;
+        mCancelling = true;
         applyChangeInsets(null);
         mCancelled = true;
         mListener.onCancelled(mReadyDispatched ? this : null);
@@ -488,15 +489,15 @@
             return;
         }
 
-        final boolean visible = mPendingFraction == 0
-                // The first frame of ANIMATION_TYPE_SHOW should be invisible since it is
-                // animated from the hidden state.
-                ? mAnimationType != ANIMATION_TYPE_SHOW
-                : mPendingFraction < 1f || (mFinished
-                        ? mShownOnFinish
-                        // If the animation is cancelled, mFinished and mShownOnFinish are not set.
+        final boolean visible = mFinished
+                ? mShownOnFinish
+                : (mCancelling
+                        // If the animation is being cancelled, mShownOnFinish is not valid.
                         // Here uses mLayoutInsetsDuringAnimation to decide if it should be visible.
-                        : mLayoutInsetsDuringAnimation == LAYOUT_INSETS_DURING_ANIMATION_SHOWN);
+                        ? mLayoutInsetsDuringAnimation == LAYOUT_INSETS_DURING_ANIMATION_SHOWN
+                        // The first frame of ANIMATION_TYPE_SHOW should be invisible since it is
+                        // animated from the hidden state.
+                        : (mAnimationType != ANIMATION_TYPE_SHOW || mPendingFraction != 0));
 
         // TODO: Implement behavior when inset spans over multiple types
         for (int i = controls.size() - 1; i >= 0; i--) {
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index e665c08..d7cf3e8 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -4865,7 +4865,7 @@
         /**
          * @hide
          */
-        public Transaction setDesintationFrame(SurfaceControl sc, @NonNull Rect destinationFrame) {
+        public Transaction setDestinationFrame(SurfaceControl sc, @NonNull Rect destinationFrame) {
             checkPreconditions(sc);
             nativeSetDestinationFrame(mNativeObject, sc.mNativeObject,
                     destinationFrame.left, destinationFrame.top, destinationFrame.right,
@@ -4876,7 +4876,7 @@
         /**
          * @hide
          */
-        public Transaction setDesintationFrame(SurfaceControl sc, int width, int height) {
+        public Transaction setDestinationFrame(SurfaceControl sc, int width, int height) {
             checkPreconditions(sc);
             nativeSetDestinationFrame(mNativeObject, sc.mNativeObject, 0, 0, width, height);
             return this;
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index b0051ce..780e761 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -1125,7 +1125,7 @@
                     }
                 }
 
-                surfaceUpdateTransaction.setDesintationFrame(mBlastSurfaceControl, mSurfaceWidth,
+                surfaceUpdateTransaction.setDestinationFrame(mBlastSurfaceControl, mSurfaceWidth,
                             mSurfaceHeight);
 
                 if (isHardwareAccelerated()) {
diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java
index 6f7660a..9d0ea54 100644
--- a/core/java/android/window/TaskFragmentOperation.java
+++ b/core/java/android/window/TaskFragmentOperation.java
@@ -161,6 +161,16 @@
      */
     public static final int OP_TYPE_SET_PINNED = 19;
 
+    /**
+     * Sets whether this TaskFragment can affect system UI flags such as the status bar. Default
+     * is {@code true}.
+     *
+     * This operation is only allowed for system organizers. See
+     * {@link com.android.server.wm.TaskFragmentOrganizerController#registerOrganizer(
+     * ITaskFragmentOrganizer, boolean)}
+     */
+    public static final int OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS = 20;
+
     @IntDef(prefix = { "OP_TYPE_" }, value = {
             OP_TYPE_UNKNOWN,
             OP_TYPE_CREATE_TASK_FRAGMENT,
@@ -183,6 +193,7 @@
             OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH,
             OP_TYPE_SET_DECOR_SURFACE_BOOSTED,
             OP_TYPE_SET_PINNED,
+            OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface OperationType {}
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index be0b4fe..b4e7675 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -463,6 +463,13 @@
 }
 
 flag {
+    name: "enable_connected_displays_pip"
+    namespace: "lse_desktop_experience"
+    description: "Enables PiP features in connected displays."
+    bug: "362721131"
+}
+
+flag {
     name: "reparent_window_token_api"
     namespace: "lse_desktop_experience"
     description: "Allows to reparent a window token to a different display"
@@ -531,6 +538,16 @@
 }
 
 flag {
+    name: "enable_per_display_package_context_cache_in_statusbar_notif"
+    namespace: "lse_desktop_experience"
+    description: "Enables per-display package context caching in StatusBarNotification"
+    bug: "388886443"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "enable_desktop_wallpaper_activity_for_system_user"
     namespace: "lse_desktop_experience"
     description: "Enables starting DesktopWallpaperActivity on system user."
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 7a1078f..8b1fd6c 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -432,3 +432,19 @@
     bug: "384976265"
 }
 
+flag {
+    name: "aod_transition"
+    namespace: "windowing_frontend"
+    description: "Support to show lock wallpaper in aod state"
+    bug: "361438779"
+}
+
+flag {
+    name: "check_disabled_snapshots_in_task_persister"
+    namespace: "windowing_frontend"
+    description: "Check for TaskSnapshots disabling in TaskSnapshotPersister."
+    bug: "387915176"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
index cf3a54b..b187eb4 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
@@ -181,7 +181,8 @@
         final InvisibleToggleAllowListingFeatureTarget magnification =
                 new InvisibleToggleAllowListingFeatureTarget(context,
                         shortcutType,
-                        isShortcutContained(context, shortcutType, MAGNIFICATION_CONTROLLER_NAME),
+                        isShortcutContained(
+                                context, shortcutType, MAGNIFICATION_CONTROLLER_NAME),
                         MAGNIFICATION_CONTROLLER_NAME,
                         uid,
                         context.getString(R.string.accessibility_magnification_chooser_text),
diff --git a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
index 14ca0f8..fc3c48d 100644
--- a/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
+++ b/core/java/com/android/internal/accessibility/util/ShortcutUtils.java
@@ -25,6 +25,7 @@
 import static com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType.INVISIBLE_TOGGLE;
 import static com.android.internal.accessibility.common.ShortcutConstants.SERVICES_SEPARATOR;
 import static com.android.internal.accessibility.common.ShortcutConstants.USER_SHORTCUT_TYPES;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.DEFAULT;
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.KEY_GESTURE;
@@ -157,19 +158,42 @@
     }
 
     /**
-     * Returns if a {@code shortcutType} shortcut contains {@code componentId}.
+     * Returns if a {@code shortcutType} shortcut contains {@code componentName}.
      *
      * @param context The current context.
      * @param shortcutType The preferred shortcut type user selected.
-     * @param componentId The component id that need to be checked.
-     * @return {@code true} if a component id is contained.
+     * @param componentName The component that need to be checked.
+     * @return {@code true} if the shortcut contains {@code componentName}.
      */
-    public static boolean isShortcutContained(Context context, @UserShortcutType int shortcutType,
-            @NonNull String componentId) {
-        final AccessibilityManager am = (AccessibilityManager) context.getSystemService(
-                Context.ACCESSIBILITY_SERVICE);
-        final List<String> requiredTargets = am.getAccessibilityShortcutTargets(shortcutType);
-        return requiredTargets.contains(componentId);
+    @SuppressLint("MissingPermission")
+    public static boolean isShortcutContained(
+            Context context, @UserShortcutType int shortcutType, @NonNull String componentName) {
+        AccessibilityManager manager = context.getSystemService(AccessibilityManager.class);
+        if (manager != null) {
+            return manager
+                    .getAccessibilityShortcutTargets(shortcutType).contains(componentName);
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Returns every shortcut type that currently has the provided componentName as a target.
+     * Types are returned as a singular flag integer.
+     * If none have the componentName, returns {@link UserShortcutType#DEFAULT}
+     */
+    public static int getEnabledShortcutTypes(
+            Context context, String componentName) {
+        final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
+        if (am == null) return DEFAULT;
+
+        int shortcutTypes = DEFAULT;
+        for (int shortcutType : USER_SHORTCUT_TYPES) {
+            if (am.getAccessibilityShortcutTargets(shortcutType).contains(componentName)) {
+                shortcutTypes |= shortcutType;
+            }
+        }
+        return shortcutTypes;
     }
 
     /**
@@ -229,8 +253,7 @@
      */
     public static void updateInvisibleToggleAccessibilityServiceEnableState(
             Context context, Set<String> componentNames, int userId) {
-        final AccessibilityManager am = (AccessibilityManager) context.getSystemService(
-                Context.ACCESSIBILITY_SERVICE);
+        final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
         if (am == null) return;
 
         final List<AccessibilityServiceInfo> installedServices =
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index d1adfc9..158b526 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -257,8 +257,16 @@
      */
     public static final int CUJ_DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU = 120;
 
+    /** Track Launcher Overview Task Dismiss animation.
+     *
+     * <p>Tracking starts when the overview task is dismissed via
+     * {@link com.android.quickstep.views.RecentsView#dismissTask}. Tracking finishes when the
+     * animation to dismiss the overview task ends.
+     */
+    public static final int CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS = 121;
+
     // When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE.
-    @VisibleForTesting static final int LAST_CUJ = CUJ_DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU;
+    @VisibleForTesting static final int LAST_CUJ = CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS;
 
     /** @hide */
     @IntDef({
@@ -370,7 +378,8 @@
             CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE,
             CUJ_DESKTOP_MODE_SNAP_RESIZE,
             CUJ_DESKTOP_MODE_UNMAXIMIZE_WINDOW,
-            CUJ_DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU
+            CUJ_DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU,
+            CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CujType {}
@@ -493,6 +502,7 @@
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_SNAP_RESIZE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_SNAP_RESIZE;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_UNMAXIMIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_UNMAXIMIZE_WINDOW;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OVERVIEW_TASK_DISMISS;
     }
 
     private Cuj() {
@@ -729,6 +739,8 @@
                 return "DESKTOP_MODE_UNMAXIMIZE_WINDOW";
             case CUJ_DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU:
                 return "DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU";
+            case CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS:
+                return "LAUNCHER_OVERVIEW_TASK_DISMISS";
         }
         return "UNKNOWN";
     }
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index 1709ca7..f6de345 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -174,7 +174,9 @@
                     // System process is dead; ignore
                 } else {
                     try {
-                        Clog_e(TAG, "Error reporting crash", t2);
+                        // Log original crash and then log the error reporting exception.
+                        Clog_e(TAG, "Couldn't report crash. Here's the crash:", e);
+                        Clog_e(TAG, "Error reporting crash. Here's the error:", t2);
                     } catch (Throwable t3) {
                         // Even Clog_e() fails!  Oh well.
                     }
diff --git a/core/java/com/android/internal/widget/NotificationProgressBar.java b/core/java/com/android/internal/widget/NotificationProgressBar.java
index 904b73f..1b77020 100644
--- a/core/java/com/android/internal/widget/NotificationProgressBar.java
+++ b/core/java/com/android/internal/widget/NotificationProgressBar.java
@@ -836,11 +836,16 @@
             } else if (part instanceof Point point) {
                 final float pointWidth = 2 * pointRadius;
                 float start = x - pointRadius;
-                if (start < 0) start = 0;
-                float end = start + pointWidth;
-                if (end > totalWidth) {
+                float end = x + pointRadius;
+                // Only shift the points right at the start/end.
+                // For the points close to the start/end, the segment minimum width requirement
+                // would take care of shifting them to be within the bounds.
+                if (x == 0) {
+                    start = 0;
+                    end = pointWidth;
+                } else if (x == totalWidth) {
+                    start = totalWidth - pointWidth;
                     end = totalWidth;
-                    if (totalWidth > pointWidth) start = totalWidth - pointWidth;
                 }
 
                 drawableParts.add(new DrawablePoint(start, end, point.mColor));
@@ -853,7 +858,7 @@
     private static float getSegStartOffset(Part prevPart, float pointRadius, float segPointGap,
             float startX) {
         if (!(prevPart instanceof Point)) return 0F;
-        final float pointOffset = (startX < pointRadius) ? (pointRadius - startX) : 0;
+        final float pointOffset = (startX == 0) ? pointRadius : 0;
         return pointOffset + pointRadius + segPointGap;
     }
 
@@ -869,9 +874,7 @@
             return segSegGap;
         }
 
-        final float pointWidth = 2 * pointRadius;
-        final float pointOffset = (endX + pointRadius > totalWidth && totalWidth > pointWidth)
-                ? (endX + pointRadius - totalWidth) : 0;
+        final float pointOffset = (endX == totalWidth) ? pointRadius : 0;
         return segPointGap + pointRadius + pointOffset;
     }
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ed021b6..4450802 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8804,22 +8804,6 @@
     <permission android:name="android.permission.RESERVED_FOR_TESTING_SIGNATURE"
                 android:protectionLevel="signature"/>
 
-    <!--
-        This permission allows the system to receive PACKAGE_CHANGED broadcasts when the component
-        state of a non-exported component has been changed.
-        <p>Not for use by third-party applications. </p>
-        <p>Protection level: internal
-        @hide
-    -->
-    <permission
-        android:name="android.permission.INTERNAL_RECEIVE_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED"
-        android:protectionLevel="internal"
-        android:featureFlag="android.content.pm.reduce_broadcasts_for_component_state_changes"/>
-
-    <uses-permission
-        android:name="android.permission.INTERNAL_RECEIVE_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED"
-        android:featureFlag="android.content.pm.reduce_broadcasts_for_component_state_changes"/>
-
     <!-- @SystemApi
         @FlaggedApi("android.media.tv.flags.kids_mode_tvdb_sharing")
         This permission is required when accessing information related to
@@ -9272,6 +9256,11 @@
                 android:permission="android.permission.BIND_JOB_SERVICE" >
         </service>
 
+        <service android:name="com.android.server.ZramMaintenance"
+                 android:exported="false"
+                 android:permission="android.permission.BIND_JOB_SERVICE" >
+        </service>
+
         <service android:name="com.android.server.ZramWriteback"
                  android:exported="false"
                  android:permission="android.permission.BIND_JOB_SERVICE" >
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 8372aec..8bf61bb 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -7590,7 +7590,7 @@
         <!-- Minimum required drawing width. The drawing width refers to the width after
          the original segments have been adjusted for the neighboring Points and gaps. This is
          enforced by stretching the segments that are too short. -->
-        <attr name="minWidth" format="dimension" />
+        <attr name="minWidth" />
         <!-- Height of the solid segments. -->
         <attr name="height" />
         <!-- Height of the faded segments. -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index e14cffd..416e0ae 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3156,6 +3156,16 @@
          with admin privileges and admin privileges can be granted/revoked from existing users. -->
     <bool name="config_enableMultipleAdmins">false</bool>
 
+    <!-- Whether to start stopped users before their scheduled alarms. If set to true, users will be
+         started in background before the alarm time so that it can go off. If false, alarms of
+         stopped users will not go off and users will remain stopped. -->
+    <bool name="config_allowAlarmsOnStoppedUsers">true</bool>
+
+    <!-- Whether notification is shown to foreground user when alarm/timer goes off on background
+         user. If set to true, foreground user will receive a notification with ability to mute
+         sound or switch user. If false, system notification will not be shown. -->
+    <bool name="config_showNotificationForBackgroundUserAlarms">true</bool>
+
     <!-- Whether there is a communal profile which should always be running.
          Only relevant for Headless System User Mode (HSUM) devices. -->
     <bool name="config_omnipresentCommunalUser">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 68008e5..84d51f0 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -366,6 +366,8 @@
   <java-symbol type="bool" name="config_canSwitchToHeadlessSystemUser"/>
   <java-symbol type="bool" name="config_enableMultiUserUI"/>
   <java-symbol type="bool" name="config_enableMultipleAdmins"/>
+  <java-symbol type="bool" name="config_allowAlarmsOnStoppedUsers"/>
+  <java-symbol type="bool" name="config_showNotificationForBackgroundUserAlarms"/>
   <java-symbol type="bool" name="config_bootToHeadlessSystemUser"/>
   <java-symbol type="bool" name="config_omnipresentCommunalUser"/>
   <java-symbol type="bool" name="config_enableNewAutoSelectNetworkUI"/>
diff --git a/core/tests/coretests/src/android/app/NotificationManagerTest.java b/core/tests/coretests/src/android/app/NotificationManagerTest.java
index 3d6e122..18ba6a1 100644
--- a/core/tests/coretests/src/android/app/NotificationManagerTest.java
+++ b/core/tests/coretests/src/android/app/NotificationManagerTest.java
@@ -19,6 +19,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeast;
@@ -269,8 +270,9 @@
 
         // 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())));
+        when(mNotificationManager.mBackendService.getOrCreateNotificationChannels(any(), any(),
+                anyInt(), anyBoolean())).thenReturn(
+                    new ParceledListSlice<>(List.of(exampleChannel())));
 
         // ask for the same channel 100 times without invalidating the cache
         for (int i = 0; i < 100; i++) {
@@ -282,7 +284,7 @@
         NotificationChannel unused = mNotificationManager.getNotificationChannel("id");
 
         verify(mNotificationManager.mBackendService, times(2))
-                .getNotificationChannels(any(), any(), anyInt());
+                .getOrCreateNotificationChannels(any(), any(), anyInt(), anyBoolean());
     }
 
     @Test
@@ -295,23 +297,24 @@
         NotificationChannel c2 = new NotificationChannel("id2", "name2",
                 NotificationManager.IMPORTANCE_NONE);
 
-        when(mNotificationManager.mBackendService.getNotificationChannels(any(), any(),
-                anyInt())).thenReturn(new ParceledListSlice<>(List.of(c1, c2)));
+        when(mNotificationManager.mBackendService.getOrCreateNotificationChannels(any(), any(),
+                anyInt(), anyBoolean())).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());
+                .getOrCreateNotificationChannels(any(), any(), anyInt(), anyBoolean());
     }
 
     @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())));
+        when(mNotificationManager.mBackendService.getOrCreateNotificationChannels(any(), any(),
+                anyInt(), anyBoolean())).thenReturn(
+                    new ParceledListSlice<>(List.of(exampleChannel())));
 
         // ask for channels 100 times without invalidating the cache
         for (int i = 0; i < 100; i++) {
@@ -323,7 +326,7 @@
         List<NotificationChannel> res = mNotificationManager.getNotificationChannels();
 
         verify(mNotificationManager.mBackendService, times(2))
-                .getNotificationChannels(any(), any(), anyInt());
+                .getOrCreateNotificationChannels(any(), any(), anyInt(), anyBoolean());
         assertThat(res).containsExactlyElementsIn(List.of(exampleChannel()));
     }
 
@@ -341,8 +344,9 @@
         NotificationChannel c2 = new NotificationChannel("other", "name2",
                 NotificationManager.IMPORTANCE_DEFAULT);
 
-        when(mNotificationManager.mBackendService.getNotificationChannels(any(), any(), anyInt()))
-                .thenReturn(new ParceledListSlice<>(List.of(c1, conv1, c2)));
+        when(mNotificationManager.mBackendService.getOrCreateNotificationChannels(any(), any(),
+                anyInt(), anyBoolean())).thenReturn(
+                    new ParceledListSlice<>(List.of(c1, conv1, c2)));
 
         // Lookup for channel c1 and c2: returned as expected
         assertThat(mNotificationManager.getNotificationChannel("id")).isEqualTo(c1);
@@ -359,9 +363,9 @@
         // Lookup of a nonexistent channel is null
         assertThat(mNotificationManager.getNotificationChannel("id3")).isNull();
 
-        // All of that should have been one call to getNotificationChannels()
+        // All of that should have been one call to getOrCreateNotificationChannels()
         verify(mNotificationManager.mBackendService, times(1))
-                .getNotificationChannels(any(), any(), anyInt());
+                .getOrCreateNotificationChannels(any(), any(), anyInt(), anyBoolean());
     }
 
     @Test
@@ -381,12 +385,12 @@
         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)));
+        when(mNotificationManager.mBackendService.getOrCreateNotificationChannels(any(), eq(pkg1),
+                eq(userId), anyBoolean())).thenReturn(new ParceledListSlice<>(List.of(channel1)));
+        when(mNotificationManager.mBackendService.getOrCreateNotificationChannels(any(), eq(pkg2),
+                eq(userId), anyBoolean())).thenReturn(new ParceledListSlice<>(List.of(channel2)));
+        when(mNotificationManager.mBackendService.getOrCreateNotificationChannels(any(), eq(pkg1),
+                eq(userId1), anyBoolean())).thenReturn(new ParceledListSlice<>(List.of(channel3)));
 
         // set our context to pretend to be from package 1 and userId 0
         mContext.setParameters(pkg1, pkg1, userId);
@@ -402,7 +406,7 @@
 
         // Those should have been three different calls
         verify(mNotificationManager.mBackendService, times(3))
-                .getNotificationChannels(any(), any(), anyInt());
+                .getOrCreateNotificationChannels(any(), any(), anyInt(), anyBoolean());
     }
 
     private Notification exampleNotification() {
diff --git a/core/tests/coretests/src/android/os/SystemHealthManagerUnitTest.java b/core/tests/coretests/src/android/os/SystemHealthManagerUnitTest.java
new file mode 100644
index 0000000..1f9d427
--- /dev/null
+++ b/core/tests/coretests/src/android/os/SystemHealthManagerUnitTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.power.CpuHeadroomResult;
+import android.hardware.power.GpuHeadroomResult;
+import android.hardware.power.SupportInfo;
+import android.os.health.SystemHealthManager;
+import android.platform.test.annotations.DisabledOnRavenwood;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.internal.app.IBatteryStats;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+@DisabledOnRavenwood(blockedBy = SystemHealthManager.class)
+public class SystemHealthManagerUnitTest {
+    @Mock
+    private IBatteryStats mBatteryStats;
+    @Mock
+    private IPowerStatsService mPowerStats;
+    @Mock
+    private IHintManager mHintManager;
+    private SystemHealthManager mSystemHealthManager;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        IHintManager.HintManagerClientData clientData = new IHintManager.HintManagerClientData();
+        clientData.supportInfo = new SupportInfo();
+        clientData.maxCpuHeadroomThreads = 10;
+        clientData.supportInfo.headroom = new SupportInfo.HeadroomSupportInfo();
+        clientData.supportInfo.headroom.isCpuSupported = true;
+        clientData.supportInfo.headroom.isGpuSupported = true;
+        clientData.supportInfo.headroom.cpuMinCalculationWindowMillis = 45;
+        clientData.supportInfo.headroom.cpuMaxCalculationWindowMillis = 9999;
+        clientData.supportInfo.headroom.gpuMinCalculationWindowMillis = 46;
+        clientData.supportInfo.headroom.gpuMaxCalculationWindowMillis = 9998;
+        clientData.supportInfo.headroom.cpuMinIntervalMillis = 999;
+        clientData.supportInfo.headroom.gpuMinIntervalMillis = 998;
+        when(mHintManager.getClientData()).thenReturn(clientData);
+        mSystemHealthManager = new SystemHealthManager(mBatteryStats, mPowerStats, mHintManager);
+    }
+
+    @Test
+    public void testHeadroomParamsValueRange() {
+        assertEquals(999, mSystemHealthManager.getCpuHeadroomMinIntervalMillis());
+        assertEquals(998, mSystemHealthManager.getGpuHeadroomMinIntervalMillis());
+        assertEquals(45, (int) mSystemHealthManager.getCpuHeadroomCalculationWindowRange().first);
+        assertEquals(9999,
+                (int) mSystemHealthManager.getCpuHeadroomCalculationWindowRange().second);
+        assertEquals(46, (int) mSystemHealthManager.getGpuHeadroomCalculationWindowRange().first);
+        assertEquals(9998,
+                (int) mSystemHealthManager.getGpuHeadroomCalculationWindowRange().second);
+        assertEquals(10, (int) mSystemHealthManager.getMaxCpuHeadroomTidsSize());
+    }
+
+    @Test
+    public void testGetCpuHeadroom() throws RemoteException, InterruptedException {
+        final CpuHeadroomParams params1 = null;
+        final CpuHeadroomParamsInternal internalParams1 = new CpuHeadroomParamsInternal();
+
+        final CpuHeadroomParams params2 = new CpuHeadroomParams.Builder()
+                .setCalculationWindowMillis(100)
+                .build();
+        final CpuHeadroomParamsInternal internalParams2 = new CpuHeadroomParamsInternal();
+        internalParams2.calculationWindowMillis = 100;
+
+        final CpuHeadroomParams params3 = new CpuHeadroomParams.Builder()
+                .setCalculationType(CpuHeadroomParams.CPU_HEADROOM_CALCULATION_TYPE_AVERAGE)
+                .build();
+        final CpuHeadroomParamsInternal internalParams3 = new CpuHeadroomParamsInternal();
+        internalParams3.calculationType =
+                (byte) CpuHeadroomParams.CPU_HEADROOM_CALCULATION_TYPE_AVERAGE;
+
+        final CpuHeadroomParams params4 = new CpuHeadroomParams.Builder()
+                .setTids(1000, 1001)
+                .build();
+        final CpuHeadroomParamsInternal internalParams4 = new CpuHeadroomParamsInternal();
+        internalParams4.tids = new int[]{1000, 1001};
+
+        when(mHintManager.getCpuHeadroom(internalParams1)).thenReturn(
+                CpuHeadroomResult.globalHeadroom(99f));
+        when(mHintManager.getCpuHeadroom(internalParams2)).thenReturn(
+                CpuHeadroomResult.globalHeadroom(98f));
+        when(mHintManager.getCpuHeadroom(internalParams3)).thenReturn(
+                CpuHeadroomResult.globalHeadroom(97f));
+        when(mHintManager.getCpuHeadroom(internalParams4)).thenReturn(null);
+
+        assertEquals(99f, mSystemHealthManager.getCpuHeadroom(params1), 0.001f);
+        assertEquals(98f, mSystemHealthManager.getCpuHeadroom(params2), 0.001f);
+        assertEquals(97f, mSystemHealthManager.getCpuHeadroom(params3), 0.001f);
+        assertTrue(Float.isNaN(mSystemHealthManager.getCpuHeadroom(params4)));
+        verify(mHintManager, times(1)).getCpuHeadroom(internalParams1);
+        verify(mHintManager, times(1)).getCpuHeadroom(internalParams2);
+        verify(mHintManager, times(1)).getCpuHeadroom(internalParams3);
+        verify(mHintManager, times(1)).getCpuHeadroom(internalParams4);
+    }
+
+    @Test
+    public void testGetGpuHeadroom() throws RemoteException, InterruptedException {
+        final GpuHeadroomParams params1 = null;
+        final GpuHeadroomParamsInternal internalParams1 = new GpuHeadroomParamsInternal();
+        final GpuHeadroomParams params2 = new GpuHeadroomParams.Builder()
+                .setCalculationWindowMillis(100)
+                .build();
+        final GpuHeadroomParamsInternal internalParams2 = new GpuHeadroomParamsInternal();
+        internalParams2.calculationWindowMillis = 100;
+        final GpuHeadroomParams params3 = new GpuHeadroomParams.Builder()
+                .setCalculationType(GpuHeadroomParams.GPU_HEADROOM_CALCULATION_TYPE_AVERAGE)
+                .build();
+        final GpuHeadroomParamsInternal internalParams3 = new GpuHeadroomParamsInternal();
+        internalParams3.calculationType =
+                (byte) GpuHeadroomParams.GPU_HEADROOM_CALCULATION_TYPE_AVERAGE;
+
+        when(mHintManager.getGpuHeadroom(internalParams1)).thenReturn(
+                GpuHeadroomResult.globalHeadroom(99f));
+        when(mHintManager.getGpuHeadroom(internalParams2)).thenReturn(
+                GpuHeadroomResult.globalHeadroom(98f));
+        when(mHintManager.getGpuHeadroom(internalParams3)).thenReturn(null);
+
+        assertEquals(99f, mSystemHealthManager.getGpuHeadroom(params1), 0.001f);
+        assertEquals(98f, mSystemHealthManager.getGpuHeadroom(params2), 0.001f);
+        assertTrue(Float.isNaN(mSystemHealthManager.getGpuHeadroom(params3)));
+        verify(mHintManager, times(1)).getGpuHeadroom(internalParams1);
+        verify(mHintManager, times(1)).getGpuHeadroom(internalParams2);
+        verify(mHintManager, times(1)).getGpuHeadroom(internalParams3);
+    }
+}
diff --git a/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java b/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java
index 5042408..7a4cc7f 100644
--- a/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java
+++ b/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java
@@ -22,6 +22,7 @@
 import static junit.framework.Assert.assertNull;
 
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
@@ -35,14 +36,19 @@
 import android.content.pm.PackageManager;
 import android.metrics.LogMaker;
 import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.view.Display;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.window.flags.Flags;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -52,11 +58,16 @@
 @SmallTest
 public class StatusBarNotificationTest {
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     private final Context mMockContext = mock(Context.class);
     @Mock
     private Context mRealContext;
     @Mock
     private PackageManager mPm;
+    @Mock
+    private Context mSecondaryDisplayContext;
 
     private static final String PKG = "com.example.o";
     private static final int UID = 9583;
@@ -80,6 +91,10 @@
                 InstrumentationRegistry.getContext().getResources());
         when(mMockContext.getPackageManager()).thenReturn(mPm);
         when(mMockContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
+        when(mMockContext.getDisplayId()).thenReturn(Display.DEFAULT_DISPLAY);
+        when(mSecondaryDisplayContext.getPackageManager()).thenReturn(mPm);
+        when(mSecondaryDisplayContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
+        when(mSecondaryDisplayContext.getDisplayId()).thenReturn(2);
         when(mPm.getApplicationLabel(any())).thenReturn("");
 
         mRealContext = InstrumentationRegistry.getContext();
@@ -221,6 +236,24 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_PER_DISPLAY_PACKAGE_CONTEXT_CACHE_IN_STATUSBAR_NOTIF)
+    public void testGetPackageContext_multipleDisplaysCase() {
+        String pkg = "com.android.systemui";
+        int uid = 1000;
+        Notification notification = getNotificationBuilder(GROUP_ID_1, CHANNEL_ID).build();
+        StatusBarNotification sbn = new StatusBarNotification(
+                pkg, pkg, ID, TAG, uid, uid, notification, UserHandle.ALL, null, UID);
+        Context defaultContext = sbn.getPackageContext(mRealContext);
+        Context secondaryContext = sbn.getPackageContext(mSecondaryDisplayContext);
+        assertNotSame(mRealContext, defaultContext);
+        assertNotSame(defaultContext, secondaryContext);
+
+        // Let's make sure it caches it:
+        assertSame(defaultContext, sbn.getPackageContext(mRealContext));
+        assertSame(secondaryContext, sbn.getPackageContext(mSecondaryDisplayContext));
+    }
+
+    @Test
     public void testGetUidFromKey() {
         StatusBarNotification sbn = getNotification("pkg", null, "channel");
 
diff --git a/core/tests/coretests/src/android/text/LayoutTest.java b/core/tests/coretests/src/android/text/LayoutTest.java
index c7d85d4..9e78af5 100644
--- a/core/tests/coretests/src/android/text/LayoutTest.java
+++ b/core/tests/coretests/src/android/text/LayoutTest.java
@@ -1024,6 +1024,55 @@
         expect.that(backgroundCommands.size()).isEqualTo(backgroundRectsDrawn);
     }
 
+    @Test
+    @RequiresFlagsEnabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
+    public void highContrastTextEnabled_testWhitespaceText_DrawsBackgroundsWithAdjacentLetters() {
+        mTextPaint.setColor(Color.BLACK);
+        SpannableString spannedText = new SpannableString("Test\tTap and Space");
+
+        // Set the entire text to white initially
+        spannedText.setSpan(
+                new ForegroundColorSpan(Color.WHITE),
+                /* start= */ 0,
+                /* end= */ spannedText.length(),
+                Spanned.SPAN_INCLUSIVE_EXCLUSIVE
+        );
+
+        // Find the whitespace character and set its color to black
+        for (int i = 0; i < spannedText.length(); i++) {
+            if (Character.isWhitespace(spannedText.charAt(i))) {
+                spannedText.setSpan(
+                        new ForegroundColorSpan(Color.BLACK),
+                        i,
+                        i + 1,
+                        Spanned.SPAN_INCLUSIVE_EXCLUSIVE
+                );
+            }
+        }
+
+        Layout layout = new StaticLayout(spannedText, mTextPaint, mWidth,
+                mAlign, mSpacingMult, mSpacingAdd, /* includePad= */ false);
+
+        MockCanvas c = new MockCanvas(/* width= */ 256, /* height= */ 256);
+        c.setHighContrastTextEnabled(true);
+        layout.draw(
+                c,
+                /* highlightPaths= */ null,
+                /* highlightPaints= */ null,
+                /* selectionPath= */ null,
+                /* selectionPaint= */ null,
+                /* cursorOffsetVertical= */ 0
+        );
+
+        List<MockCanvas.DrawCommand> drawCommands = c.getDrawCommands();
+        for (int i = 0; i < drawCommands.size(); i++) {
+            MockCanvas.DrawCommand drawCommand = drawCommands.get(i);
+            if (drawCommand.rect != null) {
+                expect.that(removeAlpha(drawCommand.paint.getColor())).isEqualTo(Color.BLACK);
+            }
+        }
+    }
+
     private int removeAlpha(int color) {
         return Color.rgb(
                 Color.red(color),
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/util/ShortcutUtilsTest.java b/core/tests/coretests/src/com/android/internal/accessibility/util/ShortcutUtilsTest.java
index 1a9af6b..d1555e3 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/util/ShortcutUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/util/ShortcutUtilsTest.java
@@ -20,8 +20,8 @@
 import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER;
 
 import static com.android.internal.accessibility.common.ShortcutConstants.SERVICES_SEPARATOR;
-import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE;
-import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.KEY_GESTURE;
+import static com.android.internal.accessibility.common.ShortcutConstants.USER_SHORTCUT_TYPES;
+import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.DEFAULT;
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -42,19 +42,22 @@
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.IAccessibilityManager;
 
-import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.internal.accessibility.AccessibilityShortcutController;
 import com.android.internal.accessibility.TestUtils;
 import com.android.internal.accessibility.common.ShortcutConstants;
 
+import com.google.testing.junit.testparameterinjector.TestParameter;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
 import java.util.StringJoiner;
@@ -62,7 +65,7 @@
 /**
  * Unit Tests for {@link com.android.internal.accessibility.util.ShortcutUtils}
  */
-@RunWith(AndroidJUnit4.class)
+@RunWith(TestParameterInjector.class)
 public class ShortcutUtilsTest {
     private static final Set<String> ONE_COMPONENT = Set.of(
             new ComponentName("pkg", "serv").flattenToString());
@@ -99,38 +102,19 @@
     }
 
     @Test
-    public void getShortcutTargets_softwareShortcutNoService_emptyResult() {
+    public void getShortcutTargets_noService_emptyResult(
+            @TestParameter(valuesProvider = ShortcutTypeValueProvider.class) int shortcutType) {
+        Settings.Secure.putStringForUser(
+                mContext.getContentResolver(),
+                ShortcutUtils.convertToKey(shortcutType), "", mContext.getUserId());
+
         assertThat(
                 ShortcutUtils.getShortcutTargetsFromSettings(
-                        mContext, SOFTWARE, mDefaultUserId)
+                        mContext, shortcutType, mDefaultUserId)
         ).isEmpty();
     }
 
-    @Test
-    public void getShortcutTargets_volumeKeyShortcutNoService_emptyResult() {
-        assertThat(
-                ShortcutUtils.getShortcutTargetsFromSettings(
-                        mContext, ShortcutConstants.UserShortcutType.HARDWARE,
-                        mDefaultUserId)
-        ).isEmpty();
-    }
-
-    @Test
-    public void getShortcutTargets_gestureShortcutNoService_emptyResult() {
-        assertThat(
-                ShortcutUtils.getShortcutTargetsFromSettings(
-                        mContext, GESTURE, mDefaultUserId)
-        ).isEmpty();
-    }
-
-    @Test
-    public void getShortcutTargets_keyGestureShortcutNoService_emptyResult() {
-        assertThat(
-                ShortcutUtils.getShortcutTargetsFromSettings(
-                        mContext, KEY_GESTURE, mDefaultUserId)
-        ).isEmpty();
-    }
-
+    // TODO 385186274: Parameterize this test.
     @Test
     public void getShortcutTargets_softwareShortcut1Service_return1Service() {
         setupShortcutTargets(ONE_COMPONENT, Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS);
@@ -143,6 +127,7 @@
         ).containsExactlyElementsIn(ONE_COMPONENT);
     }
 
+    // TODO 385186274: Parameterize this test.
     @Test
     public void getShortcutTargets_volumeShortcut2Service_return2Service() {
         setupShortcutTargets(ONE_COMPONENT, Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS);
@@ -155,6 +140,7 @@
         ).containsExactlyElementsIn(TWO_COMPONENTS);
     }
 
+    // TODO 385186274: Parameterize this test.
     @Test
     public void getShortcutTargets_tripleTapShortcut_magnificationDisabled_emptyResult() {
         enableTripleTapShortcutForMagnification(/* enable= */ false);
@@ -168,6 +154,7 @@
         ).isEmpty();
     }
 
+    // TODO 385186274: Parameterize this test.
     @Test
     public void getShortcutTargets_tripleTapShortcut_magnificationEnabled_returnMagnification() {
         setupShortcutTargets(ONE_COMPONENT, Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS);
@@ -181,6 +168,7 @@
         ).containsExactly(ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER);
     }
 
+    // TODO 385186274: Parameterize this test.
     @Test
     public void updateAccessibilityServiceStateIfNeeded_alwaysOnServiceOn_noShortcuts_serviceTurnedOff() {
         setupA11yServiceAndShortcutState(
@@ -195,6 +183,7 @@
         assertA11yServiceState(ALWAYS_ON_SERVICE_COMPONENT_NAME, /* enabled= */ false);
     }
 
+    // TODO 385186274: Parameterize this test.
     @Test
     public void updateAccessibilityServiceStateIfNeeded_alwaysOnServiceOnForBothUsers_noShortcutsForGuestUser_serviceTurnedOffForGuestUserOnly() {
         // setup arbitrary userId by add 10 to the default user id
@@ -218,6 +207,7 @@
                 ALWAYS_ON_SERVICE_COMPONENT_NAME, /* enabled= */ true, mDefaultUserId);
     }
 
+    // TODO 385186274: Parameterize this test.
     @Test
     public void updateAccessibilityServiceStateIfNeeded_alwaysOnServiceOn_hasShortcut_serviceKeepsOn() {
         setupA11yServiceAndShortcutState(
@@ -232,6 +222,7 @@
         assertA11yServiceState(ALWAYS_ON_SERVICE_COMPONENT_NAME, /* enabled= */ true);
     }
 
+    // TODO 385186274: Parameterize this test.
     @Test
     public void updateAccessibilityServiceStateIfNeeded_alwaysOnServiceOff_noShortcuts_serviceKeepsOff() {
         setupA11yServiceAndShortcutState(
@@ -246,6 +237,7 @@
         assertA11yServiceState(ALWAYS_ON_SERVICE_COMPONENT_NAME, /* enabled= */ false);
     }
 
+    // TODO 385186274: Parameterize this test.
     @Test
     public void updateAccessibilityServiceStateIfNeeded_alwaysOnServiceOff_hasShortcuts_serviceTurnsOn() {
         setupA11yServiceAndShortcutState(
@@ -260,6 +252,7 @@
         assertA11yServiceState(ALWAYS_ON_SERVICE_COMPONENT_NAME, /* enabled= */ true);
     }
 
+    // TODO 385186274: Parameterize this test.
     @Test
     public void updateAccessibilityServiceStateIfNeeded_standardA11yServiceOn_noShortcuts_serviceKeepsOn() {
         setupA11yServiceAndShortcutState(
@@ -274,6 +267,7 @@
         assertA11yServiceState(STANDARD_SERVICE_COMPONENT_NAME, /* enabled= */ true);
     }
 
+    // TODO 385186274: Parameterize this test.
     @Test
     public void updateAccessibilityServiceStateIfNeeded_standardA11yServiceOn_hasShortcuts_serviceKeepsOn() {
         setupA11yServiceAndShortcutState(
@@ -288,6 +282,7 @@
         assertA11yServiceState(STANDARD_SERVICE_COMPONENT_NAME, /* enabled= */ true);
     }
 
+    // TODO 385186274: Parameterize this test.
     @Test
     public void updateAccessibilityServiceStateIfNeeded_standardA11yServiceOff_noShortcuts_serviceKeepsOff() {
         setupA11yServiceAndShortcutState(
@@ -302,6 +297,7 @@
         assertA11yServiceState(STANDARD_SERVICE_COMPONENT_NAME, /* enabled= */ false);
     }
 
+    // TODO 385186274: Parameterize this test.
     @Test
     public void updateAccessibilityServiceStateIfNeeded_standardA11yServiceOff_hasShortcuts_serviceKeepsOff() {
         setupA11yServiceAndShortcutState(
@@ -316,6 +312,37 @@
         assertA11yServiceState(STANDARD_SERVICE_COMPONENT_NAME, /* enabled= */ false);
     }
 
+    @Test
+    public void getEnabledShortcutTypes_oneShortcut_returnsExpectedType(
+            @TestParameter(valuesProvider = ShortcutTypeValueProvider.class) int shortcutType)
+            throws RemoteException {
+        clearMockShortcutTypes();
+        assertThat(ShortcutUtils.getEnabledShortcutTypes(
+                mContext, STANDARD_SERVICE_COMPONENT_NAME)).isEqualTo(DEFAULT);
+        mockShortcutType(shortcutType, STANDARD_SERVICE_COMPONENT_NAME);
+        assertThat(ShortcutUtils.getEnabledShortcutTypes(
+                mContext, STANDARD_SERVICE_COMPONENT_NAME)).isEqualTo(shortcutType);
+
+    }
+
+    @Test
+    public void getEnabledShortcutTypes_twoShortcuts_returnsExpectedTypes(
+            @TestParameter(valuesProvider = ShortcutTypeValueProvider.class) int shortcutType1,
+            @TestParameter(valuesProvider = ShortcutTypeValueProvider.class) int shortcutType2
+    ) throws RemoteException {
+        if (shortcutType1 == shortcutType2) {
+            return;
+        }
+        clearMockShortcutTypes();
+        assertThat(ShortcutUtils.getEnabledShortcutTypes(
+                mContext, STANDARD_SERVICE_COMPONENT_NAME)).isEqualTo(DEFAULT);
+        mockShortcutType(shortcutType1, STANDARD_SERVICE_COMPONENT_NAME);
+        mockShortcutType(shortcutType2, STANDARD_SERVICE_COMPONENT_NAME);
+        assertThat(ShortcutUtils.getEnabledShortcutTypes(
+                mContext, STANDARD_SERVICE_COMPONENT_NAME)).isEqualTo(
+                shortcutType1 | shortcutType2);
+    }
+
     private void setupShortcutTargets(Set<String> components, String shortcutSettingsKey) {
         final StringJoiner stringJoiner = new StringJoiner(String.valueOf(SERVICES_SEPARATOR));
         for (String target : components) {
@@ -403,4 +430,29 @@
                 add ? a11yServiceComponentName : "",
                 userId);
     }
+
+    private void clearMockShortcutTypes() throws RemoteException {
+        for (int shortcutType : ShortcutConstants.USER_SHORTCUT_TYPES) {
+            when(mAccessibilityManagerService
+                    .getAccessibilityShortcutTargets(shortcutType)).thenReturn(List.of());
+        }
+    }
+
+    private void mockShortcutType(int shortcutType, String componentName)
+            throws RemoteException {
+        when(mAccessibilityManagerService.getAccessibilityShortcutTargets(shortcutType))
+                .thenReturn(List.of(componentName));
+    }
+
+    static final class ShortcutTypeValueProvider implements
+            TestParameter.TestParameterValuesProvider {
+        @Override
+        public List<Integer> provideValues() {
+            List<Integer> values = new ArrayList<>();
+            for (int shortcutType: USER_SHORTCUT_TYPES) {
+                values.add(shortcutType);
+            }
+            return values;
+        }
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
index 5df2c12..9818e19 100644
--- a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java
@@ -336,10 +336,14 @@
                 progress, progressMax);
 
         List<Part> expectedParts = new ArrayList<>(
-                List.of(new Segment(0.15f, Color.BLUE), new Point(Color.RED),
-                        new Segment(0.10f, Color.BLUE), new Point(Color.BLUE),
-                        new Segment(0.35f, Color.BLUE), new Point(Color.BLUE),
-                        new Segment(0.15f, Color.BLUE), new Point(Color.YELLOW),
+                List.of(new Segment(0.15f, Color.BLUE),
+                        new Point(Color.RED),
+                        new Segment(0.10f, Color.BLUE),
+                        new Point(Color.BLUE),
+                        new Segment(0.35f, Color.BLUE),
+                        new Point(Color.BLUE),
+                        new Segment(0.15f, Color.BLUE),
+                        new Point(Color.YELLOW),
                         new Segment(0.25f, Color.BLUE)));
 
         assertThat(parts).isEqualTo(expectedParts);
@@ -408,11 +412,16 @@
                 progress, progressMax);
 
         List<Part> expectedParts = new ArrayList<>(
-                List.of(new Segment(0.15f, Color.RED), new Point(Color.RED),
-                        new Segment(0.10f, Color.RED), new Point(Color.BLUE),
-                        new Segment(0.25f, Color.RED), new Segment(0.10f, Color.GREEN),
-                        new Point(Color.BLUE), new Segment(0.15f, Color.GREEN),
-                        new Point(Color.YELLOW), new Segment(0.25f, Color.GREEN)));
+                List.of(new Segment(0.15f, Color.RED),
+                        new Point(Color.RED),
+                        new Segment(0.10f, Color.RED),
+                        new Point(Color.BLUE),
+                        new Segment(0.25f, Color.RED),
+                        new Segment(0.10f, Color.GREEN),
+                        new Point(Color.BLUE),
+                        new Segment(0.15f, Color.GREEN),
+                        new Point(Color.YELLOW),
+                        new Segment(0.25f, Color.GREEN)));
 
         assertThat(parts).isEqualTo(expectedParts);
 
@@ -464,6 +473,158 @@
     }
 
     @Test
+    public void processAndConvertToParts_multipleSegmentsWithPointsAtStartAndEnd() {
+        List<ProgressStyle.Segment> segments = new ArrayList<>();
+        segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
+        segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
+        List<ProgressStyle.Point> points = new ArrayList<>();
+        points.add(new ProgressStyle.Point(0).setColor(Color.RED));
+        points.add(new ProgressStyle.Point(25).setColor(Color.BLUE));
+        points.add(new ProgressStyle.Point(60).setColor(Color.BLUE));
+        points.add(new ProgressStyle.Point(100).setColor(Color.YELLOW));
+        int progress = 60;
+        int progressMax = 100;
+
+        List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
+                progress, progressMax);
+
+        List<Part> expectedParts = new ArrayList<>(
+                List.of(new Point(Color.RED),
+                        new Segment(0.25f, Color.RED),
+                        new Point(Color.BLUE),
+                        new Segment(0.25f, Color.RED),
+                        new Segment(0.10f, Color.GREEN),
+                        new Point(Color.BLUE),
+                        new Segment(0.4f, Color.GREEN),
+                        new Point(Color.YELLOW)));
+
+        assertThat(parts).isEqualTo(expectedParts);
+
+        float drawableWidth = 300;
+        float segSegGap = 4;
+        float segPointGap = 4;
+        float pointRadius = 6;
+        boolean hasTrackerIcon = true;
+        List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
+                parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+        List<DrawablePart> expectedDrawableParts = new ArrayList<>(
+                List.of(new DrawablePoint(0, 12, Color.RED),
+                        new DrawableSegment(16, 65, Color.RED),
+                        new DrawablePoint(69, 81, Color.BLUE),
+                        new DrawableSegment(85, 146, Color.RED),
+                        new DrawableSegment(150, 170, Color.GREEN),
+                        new DrawablePoint(174, 186, Color.BLUE),
+                        new DrawableSegment(190, 284, Color.GREEN),
+                        new DrawablePoint(288, 300, Color.YELLOW)));
+
+        assertThat(drawableParts).isEqualTo(expectedDrawableParts);
+
+        float segmentMinWidth = 16;
+        boolean isStyledByProgress = true;
+
+        Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
+                parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
+                300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+
+        // Colors with 40% opacity
+        int fadedGreen = 0x6600FF00;
+        int fadedYellow = 0x66FFFF00;
+        expectedDrawableParts = new ArrayList<>(
+                List.of(new DrawablePoint(0, 12, Color.RED),
+                        new DrawableSegment(16, 65, Color.RED),
+                        new DrawablePoint(69, 81, Color.BLUE),
+                        new DrawableSegment(85, 146, Color.RED),
+                        new DrawableSegment(150, 170, Color.GREEN),
+                        new DrawablePoint(174, 186, Color.BLUE),
+                        new DrawableSegment(190, 284, fadedGreen, true),
+                        new DrawablePoint(288, 300, fadedYellow)));
+
+        assertThat(p.second).isEqualTo(180);
+        assertThat(p.first).isEqualTo(expectedDrawableParts);
+    }
+
+    // The points are so close to start/end that they would go out of bounds without the minimum
+    // segment width requirement.
+    @Test
+    public void processAndConvertToParts_multipleSegmentsWithPointsNearStartAndEnd() {
+        List<ProgressStyle.Segment> segments = new ArrayList<>();
+        segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
+        segments.add(new ProgressStyle.Segment(50).setColor(Color.GREEN));
+        List<ProgressStyle.Point> points = new ArrayList<>();
+        points.add(new ProgressStyle.Point(1).setColor(Color.RED));
+        points.add(new ProgressStyle.Point(25).setColor(Color.BLUE));
+        points.add(new ProgressStyle.Point(60).setColor(Color.BLUE));
+        points.add(new ProgressStyle.Point(99).setColor(Color.YELLOW));
+        int progress = 60;
+        int progressMax = 100;
+
+        List<Part> parts = NotificationProgressBar.processAndConvertToViewParts(segments, points,
+                progress, progressMax);
+
+        List<Part> expectedParts = new ArrayList<>(
+                List.of(new Segment(0.01f, Color.RED),
+                        new Point(Color.RED),
+                        new Segment(0.24f, Color.RED),
+                        new Point(Color.BLUE),
+                        new Segment(0.25f, Color.RED),
+                        new Segment(0.10f, Color.GREEN),
+                        new Point(Color.BLUE),
+                        new Segment(0.39f, Color.GREEN),
+                        new Point(Color.YELLOW),
+                        new Segment(0.01f, Color.GREEN)));
+
+        assertThat(parts).isEqualTo(expectedParts);
+
+        float drawableWidth = 300;
+        float segSegGap = 4;
+        float segPointGap = 4;
+        float pointRadius = 6;
+        boolean hasTrackerIcon = true;
+        List<DrawablePart> drawableParts = NotificationProgressBar.processAndConvertToDrawableParts(
+                parts, drawableWidth, segSegGap, segPointGap, pointRadius, hasTrackerIcon);
+
+        List<DrawablePart> expectedDrawableParts = new ArrayList<>(
+                List.of(new DrawableSegment(0, -7, Color.RED),
+                        new DrawablePoint(-3, 9, Color.RED),
+                        new DrawableSegment(13, 65, Color.RED),
+                        new DrawablePoint(69, 81, Color.BLUE),
+                        new DrawableSegment(85, 146, Color.RED),
+                        new DrawableSegment(150, 170, Color.GREEN),
+                        new DrawablePoint(174, 186, Color.BLUE),
+                        new DrawableSegment(190, 287, Color.GREEN),
+                        new DrawablePoint(291, 303, Color.YELLOW),
+                        new DrawableSegment(307, 300, Color.GREEN)));
+
+        assertThat(drawableParts).isEqualTo(expectedDrawableParts);
+
+        float segmentMinWidth = 16;
+        boolean isStyledByProgress = true;
+
+        Pair<List<DrawablePart>, Float> p = NotificationProgressBar.maybeStretchAndRescaleSegments(
+                parts, drawableParts, segmentMinWidth, pointRadius, (float) progress / progressMax,
+                300, isStyledByProgress, hasTrackerIcon ? 0 : segSegGap);
+
+        // Colors with 40% opacity
+        int fadedGreen = 0x6600FF00;
+        int fadedYellow = 0x66FFFF00;
+        expectedDrawableParts = new ArrayList<>(
+                List.of(new DrawableSegment(0, 16, Color.RED),
+                        new DrawablePoint(20, 32, Color.RED),
+                        new DrawableSegment(36, 78.02409F, Color.RED),
+                        new DrawablePoint(82.02409F, 94.02409F, Color.BLUE),
+                        new DrawableSegment(98.02409F, 146.55421F, Color.RED),
+                        new DrawableSegment(150.55421F, 169.44579F, Color.GREEN),
+                        new DrawablePoint(173.44579F, 185.44579F, Color.BLUE),
+                        new DrawableSegment(189.44579F, 264, fadedGreen, true),
+                        new DrawablePoint(268, 280, fadedYellow),
+                        new DrawableSegment(284, 300, fadedGreen, true)));
+
+        assertThat(p.second).isEqualTo(179.44579F);
+        assertThat(p.first).isEqualTo(expectedDrawableParts);
+    }
+
+    @Test
     public void processAndConvertToParts_multipleSegmentsWithPoints_notStyledByProgress() {
         List<ProgressStyle.Segment> segments = new ArrayList<>();
         segments.add(new ProgressStyle.Segment(50).setColor(Color.RED));
diff --git a/data/etc/preinstalled-packages-platform.xml b/data/etc/preinstalled-packages-platform.xml
index 7823277..3b40148 100644
--- a/data/etc/preinstalled-packages-platform.xml
+++ b/data/etc/preinstalled-packages-platform.xml
@@ -102,7 +102,7 @@
 to pre-existing users, but cannot uninstall pre-existing system packages from pre-existing users.
 -->
 <config>
-    <!--  Bluetooth (com.android.btservices apex) - visible on the sharesheet -->
+    <!--  Bluetooth (com.android.bt apex) - visible on the sharesheet -->
     <install-in-user-type package="com.android.bluetooth">
         <install-in user-type="SYSTEM" />
         <install-in user-type="FULL" />
@@ -134,4 +134,9 @@
     <install-in-user-type package="com.android.avatarpicker">
         <install-in user-type="FULL" />
     </install-in-user-type>
+
+    <!-- Users Widget (Users widget)-->
+    <install-in-user-type package="com.android.multiuser">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
 </config>
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index e6c652c..5e93f8d 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -20,6 +20,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.content.Context;
 import android.hardware.security.keymint.EcCurve;
 import android.hardware.security.keymint.KeyParameter;
@@ -732,6 +733,8 @@
         }
     }
 
+    @RequiresPermission(value = android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+            conditional = true)
     private void addAttestationParameters(@NonNull List<KeyParameter> params)
             throws ProviderException, IllegalArgumentException, DeviceIdAttestationException {
         byte[] challenge = mSpec.getAttestationChallenge();
@@ -824,7 +827,13 @@
                         break;
                     }
                     case AttestationUtils.ID_TYPE_MEID: {
-                        final String meid = telephonyService.getMeid(0);
+                        String meid;
+                        try {
+                            meid = telephonyService.getMeid(0);
+                        } catch (UnsupportedOperationException e) {
+                            Log.e(TAG, "Unable to retrieve MEID", e);
+                            meid = null;
+                        }
                         if (meid == null) {
                             throw new DeviceIdAttestationException("Unable to retrieve MEID");
                         }
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 957d1b8..bcb6c4f5 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -170,9 +170,9 @@
         "res",
     ],
     static_libs: [
+        "//frameworks/base/packages/SystemUI/aconfig:com_android_systemui_flags_lib",
         "//frameworks/libs/systemui:com_android_systemui_shared_flags_lib",
         "//frameworks/libs/systemui:iconloader_base",
-        "//packages/apps/Car/SystemUI/aconfig:com_android_systemui_car_flags_lib",
         "PlatformAnimationLib",
         "WindowManager-Shell-lite-proto",
         "WindowManager-Shell-proto",
diff --git a/libs/WindowManager/Shell/AndroidManifest.xml b/libs/WindowManager/Shell/AndroidManifest.xml
index 4f1cd97..9c15319 100644
--- a/libs/WindowManager/Shell/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/AndroidManifest.xml
@@ -32,7 +32,7 @@
         <activity
             android:name=".desktopmode.DesktopWallpaperActivity"
             android:excludeFromRecents="true"
-            android:launchMode="singleInstance"
+            android:launchMode="singleInstancePerTask"
             android:showForAllUsers="true"
             android:theme="@style/DesktopWallpaperTheme" />
 
diff --git a/libs/WindowManager/Shell/aconfig/Android.bp b/libs/WindowManager/Shell/aconfig/Android.bp
index 7f8f57b..f8da7fa 100644
--- a/libs/WindowManager/Shell/aconfig/Android.bp
+++ b/libs/WindowManager/Shell/aconfig/Android.bp
@@ -4,6 +4,7 @@
     container: "system",
     srcs: [
         "multitasking.aconfig",
+        "automotive.aconfig",
     ],
 }
 
diff --git a/libs/WindowManager/Shell/aconfig/automotive.aconfig b/libs/WindowManager/Shell/aconfig/automotive.aconfig
new file mode 100644
index 0000000..2f25aa4
--- /dev/null
+++ b/libs/WindowManager/Shell/aconfig/automotive.aconfig
@@ -0,0 +1,11 @@
+# proto-file: build/make/tools/aconfig/aconfig_protos/protos/aconfig.proto
+
+package: "com.android.wm.shell"
+container: "system"
+
+flag {
+    name: "enable_auto_task_stack_controller"
+    namespace: "multitasking"
+    description: "Enables auto task stack controller to manage task stacks on automotive"
+    bug: "384082238"
+}
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 688bf83..b10b099 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -13,13 +13,6 @@
 }
 
 flag {
-    name: "enable_split_contextual"
-    namespace: "multitasking"
-    description: "Enables invoking split contextually"
-    bug: "276361926"
-}
-
-flag {
     name: "enable_taskbar_navbar_unification"
     namespace: "multitasking"
     description: "Enables taskbar / navbar unification"
@@ -104,6 +97,13 @@
 }
 
 flag {
+    name: "enable_create_any_bubble"
+    namespace: "multitasking"
+    description: "Enable UI affordances to create bubbles via launcher app icons"
+    bug: "385220199"
+}
+
+flag {
     name: "only_reuse_bubbled_task_when_launched_from_bubble"
     namespace: "multitasking"
     description: "Allow reusing bubbled tasks for new activities only when launching from bubbles"
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt
index c45f690..117ede0 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt
@@ -290,11 +290,10 @@
 
         assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
 
-        val bbevBottom = bbev.contentBottomOnScreen + bubblePositioner.insets.top
         activityScenario.onActivity {
             // notify that the IME top coordinate is greater than the bottom of the expanded view.
             // there's no overlap so it should not be clipped.
-            animationHelper.onImeTopChanged(bbevBottom * 2)
+            animationHelper.onImeTopChanged(bbev.contentBottomOnScreen * 2)
         }
         val outline = Outline()
         bbev.outlineProvider.getOutline(bbev, outline)
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml
index 7347fbad..fc8b2991 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml
@@ -38,7 +38,7 @@
         android:layout_height="wrap_content"
         android:layout_marginTop="@dimen/bubble_popup_text_margin"
         android:maxWidth="@dimen/bubble_popup_content_max_width"
-        android:maxLines="1"
+        android:maxLines="2"
         android:ellipsize="end"
         android:textAppearance="@android:style/TextAppearance.DeviceDefault.Headline"
         android:textColor="@androidprv:color/materialColorOnSurface"
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml
index f0e18711..1616707 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml
@@ -38,7 +38,7 @@
         android:layout_height="wrap_content"
         android:layout_marginTop="@dimen/bubble_popup_text_margin"
         android:maxWidth="@dimen/bubble_popup_content_max_width"
-        android:maxLines="1"
+        android:maxLines="2"
         android:ellipsize="end"
         android:textAppearance="@android:style/TextAppearance.DeviceDefault.Headline"
         android:textColor="@androidprv:color/materialColorOnSurface"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutController.java
index 8cd7b0f..10023c9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutController.java
@@ -16,7 +16,9 @@
 
 package com.android.wm.shell.appzoomout;
 
+import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.systemui.Flags.spatialModelAppPushback;
 
 import android.app.ActivityManager;
 import android.app.WindowConfiguration;
@@ -92,7 +94,9 @@
         mDisplayAreaOrganizer = displayAreaOrganizer;
         mMainExecutor = mainExecutor;
 
-        shellInit.addInitCallback(this::onInit, this);
+        if (spatialModelAppPushback()) {
+            shellInit.addInitCallback(this::onInit, this);
+        }
     }
 
     private void onInit() {
@@ -100,6 +104,7 @@
 
         mDisplayController.addDisplayWindowListener(mDisplaysChangedListener);
         mDisplayController.addDisplayChangingController(this);
+        updateDisplayLayout(mContext.getDisplayId());
 
         mDisplayAreaOrganizer.registerOrganizer();
     }
@@ -135,7 +140,9 @@
     public void onDisplayChange(int displayId, int fromRotation, int toRotation,
             @Nullable DisplayAreaInfo newDisplayAreaInfo, WindowContainerTransaction wct) {
         // TODO: verify if there is synchronization issues.
-        mDisplayAreaOrganizer.onRotateDisplay(mContext, toRotation);
+        if (toRotation != ROTATION_UNDEFINED) {
+            mDisplayAreaOrganizer.onRotateDisplay(mContext, toRotation);
+        }
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackControllerImpl.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackControllerImpl.kt
index f8f2842..8171312 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackControllerImpl.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackControllerImpl.kt
@@ -33,7 +33,7 @@
 import android.window.TransitionInfo
 import android.window.TransitionRequestInfo
 import android.window.WindowContainerTransaction
-import com.android.systemui.car.Flags.autoTaskStackWindowing
+import com.android.wm.shell.Flags.enableAutoTaskStackController
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.common.ShellExecutor
@@ -66,7 +66,7 @@
     private val defaultRootTaskPerDisplay = mutableMapOf<Int, Int>()
 
     init {
-        if (!autoTaskStackWindowing()) {
+        if (!enableAutoTaskStackController()) {
             throw IllegalStateException("Failed to initialize" +
                     "AutoTaskStackController as the auto_task_stack_windowing TS flag is disabled.")
         } else {
@@ -220,7 +220,7 @@
         displayId: Int,
         listener: RootTaskStackListener
     ) {
-        if (!autoTaskStackWindowing()) {
+        if (!enableAutoTaskStackController()) {
             Slog.e(
                 TAG, "Failed to create root task stack as the " +
                         "auto_task_stack_windowing TS flag is disabled."
@@ -236,7 +236,7 @@
     }
 
     override fun setDefaultRootTaskStackOnDisplay(displayId: Int, rootTaskStackId: Int?) {
-        if (!autoTaskStackWindowing()) {
+        if (!enableAutoTaskStackController()) {
             Slog.e(
                 TAG, "Failed to set default root task stack as the " +
                         "auto_task_stack_windowing TS flag is disabled."
@@ -280,7 +280,7 @@
     }
 
     override fun startTransition(transaction: AutoTaskStackTransaction): IBinder? {
-        if (!autoTaskStackWindowing()) {
+        if (!enableAutoTaskStackController()) {
             Slog.e(
                 TAG, "Failed to start transaction as the " +
                         "auto_task_stack_windowing TS flag is disabled."
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
index 862906a..6299531 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
@@ -136,23 +136,15 @@
 
         // Update bitmap
         val fg = InsetDrawable(overflowBtn?.iconDrawable, overflowIconInset)
-        bitmap =
-            iconFactory
-                .createBadgedIconBitmap(AdaptiveIconDrawable(ColorDrawable(colorAccent), fg))
-                .icon
+        val drawable = AdaptiveIconDrawable(ColorDrawable(colorAccent), fg)
+        bitmap = iconFactory.createBadgedIconBitmap(drawable).icon
 
         // Update dot path
         dotPath =
             PathParser.createPathFromPathData(
                 res.getString(com.android.internal.R.string.config_icon_mask)
             )
-        val scale =
-            iconFactory.normalizer.getScale(
-                iconView!!.iconDrawable,
-                null /* outBounds */,
-                null /* path */,
-                null /* outMaskShape */
-            )
+        val scale = iconFactory.normalizer.getScale(iconView!!.iconDrawable)
         val radius = BadgedImageView.DEFAULT_PATH_SIZE / 2f
         val matrix = Matrix()
         matrix.setScale(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index e073b02..ac5b9c9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -674,9 +674,7 @@
         if (mTaskView != null) {
             mTaskView.getBoundsOnScreen(mTempBounds);
         }
-        // return the bottom of the content rect, adjusted for insets so the result is in screen
-        // coordinate
-        return mTempBounds.bottom + mPositioner.getInsets().top;
+        return mTempBounds.bottom;
     }
 
     /** Update the amount by which to clip the expanded view at the bottom. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ComponentUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ComponentUtils.kt
new file mode 100644
index 0000000..67592e6
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ComponentUtils.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.wm.shell.common
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Intent
+import com.android.wm.shell.ShellTaskOrganizer
+
+/** Utils to obtain [ComponentName]s. */
+object ComponentUtils {
+    /** Retrieves the package name from an [Intent].  */
+    @JvmStatic
+    fun getPackageName(intent: Intent?): String? = intent?.component?.packageName
+
+    /** Retrieves the package name from a [PendingIntent].  */
+    @JvmStatic
+    fun getPackageName(pendingIntent: PendingIntent?): String? =
+        getPackageName(pendingIntent?.intent)
+
+    /** Retrieves the package name from a [taskId].  */
+    @JvmStatic
+    fun getPackageName(taskId: Int, taskOrganizer: ShellTaskOrganizer): String? {
+        val taskInfo = taskOrganizer.getRunningTaskInfo(taskId)
+        return getPackageName(taskInfo?.baseIntent)
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index eb1e727..c9890a5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -447,8 +447,10 @@
             }
         }
 
-        private int imeTop(float surfaceOffset) {
-            return mImeFrame.top + (int) surfaceOffset;
+        private int imeTop(float surfaceOffset, float surfacePositionY) {
+            // surfaceOffset is already offset by the surface's top inset, so we need to subtract
+            // the top inset so that the return value is in screen coordinates.
+            return mImeFrame.top + (int) (surfaceOffset - surfacePositionY);
         }
 
         private boolean calcIsFloating(InsetsSource imeSource) {
@@ -581,7 +583,7 @@
                 final float alpha = (mAnimateAlpha || isFloating)
                         ? (value - hiddenY) / (shownY - hiddenY) : 1f;
                 t.setAlpha(animatingLeash, alpha);
-                dispatchPositionChanged(mDisplayId, imeTop(value), t);
+                dispatchPositionChanged(mDisplayId, imeTop(value, defaultY), t);
                 t.apply();
                 mTransactionPool.release(t);
             });
@@ -600,11 +602,12 @@
                     t.setPosition(animatingLeash, x, value);
                     if (DEBUG) {
                         Slog.d(TAG, "onAnimationStart d:" + mDisplayId + " top:"
-                                + imeTop(hiddenY) + "->" + imeTop(shownY)
+                                + imeTop(hiddenY, defaultY) + "->" + imeTop(shownY, defaultY)
                                 + " showing:" + (mAnimationDirection == DIRECTION_SHOW));
                     }
-                    int flags = dispatchStartPositioning(mDisplayId, imeTop(hiddenY),
-                            imeTop(shownY), mAnimationDirection == DIRECTION_SHOW, isFloating, t);
+                    int flags = dispatchStartPositioning(mDisplayId, imeTop(hiddenY, defaultY),
+                            imeTop(shownY, defaultY), mAnimationDirection == DIRECTION_SHOW,
+                            isFloating, t);
                     mAnimateAlpha = (flags & ImePositionProcessor.IME_ANIMATION_NO_ALPHA) == 0;
                     final float alpha = (mAnimateAlpha || isFloating)
                             ? (value - hiddenY) / (shownY - hiddenY)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
index 9113c0a..83e5e31 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
@@ -27,14 +27,10 @@
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
 
 import android.app.ActivityManager;
-import android.app.PendingIntent;
-import android.content.Intent;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Rect;
 
-import androidx.annotation.Nullable;
-
 import com.android.internal.util.ArrayUtils;
 import com.android.wm.shell.Flags;
 import com.android.wm.shell.ShellTaskOrganizer;
@@ -65,31 +61,6 @@
                 && ArrayUtils.contains(CONTROLLED_WINDOWING_MODES, taskInfo.getWindowingMode());
     }
 
-    /** Retrieve package name from an intent */
-    @Nullable
-    public static String getPackageName(Intent intent) {
-        if (intent == null || intent.getComponent() == null) {
-            return null;
-        }
-        return intent.getComponent().getPackageName();
-    }
-
-    /** Retrieve package name from a PendingIntent */
-    @Nullable
-    public static String getPackageName(PendingIntent pendingIntent) {
-        if (pendingIntent == null || pendingIntent.getIntent() == null) {
-            return null;
-        }
-        return getPackageName(pendingIntent.getIntent());
-    }
-
-    /** Retrieve package name from a taskId */
-    @Nullable
-    public static String getPackageName(int taskId, ShellTaskOrganizer taskOrganizer) {
-        final ActivityManager.RunningTaskInfo taskInfo = taskOrganizer.getRunningTaskInfo(taskId);
-        return taskInfo != null ? getPackageName(taskInfo.baseIntent) : null;
-    }
-
     /** Retrieve user id from a taskId */
     public static int getUserId(int taskId, ShellTaskOrganizer taskOrganizer) {
         final ActivityManager.RunningTaskInfo taskInfo = taskOrganizer.getRunningTaskInfo(taskId);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index e8add56..ac510f8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -946,8 +946,7 @@
             FocusTransitionObserver focusTransitionObserver,
             DesktopModeEventLogger desktopModeEventLogger,
             DesktopModeUiEventLogger desktopModeUiEventLogger,
-            WindowDecorTaskResourceLoader taskResourceLoader,
-            RecentsTransitionHandler recentsTransitionHandler
+            WindowDecorTaskResourceLoader taskResourceLoader
     ) {
         if (!DesktopModeStatus.canEnterDesktopModeOrShowAppHandle(context)) {
             return Optional.empty();
@@ -963,7 +962,7 @@
                 desktopTasksLimiter, appHandleEducationController, appToWebEducationController,
                 windowDecorCaptionHandleRepository, activityOrientationChangeHandler,
                 focusTransitionObserver, desktopModeEventLogger, desktopModeUiEventLogger,
-                taskResourceLoader, recentsTransitionHandler));
+                taskResourceLoader));
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 73d1527..f38957e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -552,7 +552,11 @@
         )
         val wct = WindowContainerTransaction()
         exitSplitIfApplicable(wct, taskInfo)
-        moveHomeTask(wct, toTop = true)
+        if (Flags.enablePerDisplayDesktopWallpaperActivity()) {
+            moveHomeTask(wct, toTop = true, taskInfo.displayId)
+        } else {
+            moveHomeTask(wct, toTop = true)
+        }
         val taskIdToMinimize =
             bringDesktopAppsToFrontBeforeShowingNewTask(taskInfo.displayId, wct, taskInfo.taskId)
         addMoveToDesktopChanges(wct, taskInfo)
@@ -1309,11 +1313,15 @@
     ): Int? {
         logV("bringDesktopAppsToFront, newTaskId=%d", newTaskIdInFront)
         // Move home to front, ensures that we go back home when all desktop windows are closed
-        moveHomeTask(wct, toTop = true)
+        if (Flags.enablePerDisplayDesktopWallpaperActivity()) {
+            moveHomeTask(wct, toTop = true, displayId)
+        } else {
+            moveHomeTask(wct, toTop = true)
+        }
 
         // Currently, we only handle the desktop on the default display really.
         if (
-            (displayId == DEFAULT_DISPLAY || Flags.enableBugFixesForSecondaryDisplay()) &&
+            (displayId == DEFAULT_DISPLAY || Flags.enablePerDisplayDesktopWallpaperActivity()) &&
                 ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()
         ) {
             // Add translucent wallpaper activity to show the wallpaper underneath
@@ -1359,9 +1367,13 @@
         return taskIdToMinimize
     }
 
-    private fun moveHomeTask(wct: WindowContainerTransaction, toTop: Boolean) {
+    private fun moveHomeTask(
+        wct: WindowContainerTransaction,
+        toTop: Boolean,
+        displayId: Int = DEFAULT_DISPLAY,
+    ) {
         shellTaskOrganizer
-            .getRunningTasks(context.displayId)
+            .getRunningTasks(displayId)
             .firstOrNull { task -> task.activityType == ACTIVITY_TYPE_HOME }
             ?.let { homeTask -> wct.reorder(homeTask.getToken(), /* onTop= */ toTop) }
     }
@@ -1370,12 +1382,19 @@
         logV("addWallpaperActivity")
         if (Flags.enableDesktopWallpaperActivityForSystemUser()) {
             val intent = Intent(context, DesktopWallpaperActivity::class.java)
+            if (
+                desktopWallpaperActivityTokenProvider.getToken(displayId) == null &&
+                    Flags.enablePerDisplayDesktopWallpaperActivity()
+            ) {
+                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
+            }
             val options =
                 ActivityOptions.makeBasic().apply {
                     launchWindowingMode = WINDOWING_MODE_FULLSCREEN
                     pendingIntentBackgroundActivityStartMode =
                         ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
-                    if (Flags.enableBugFixesForSecondaryDisplay()) {
+                    if (Flags.enablePerDisplayDesktopWallpaperActivity()) {
                         launchDisplayId = displayId
                     }
                 }
@@ -1391,13 +1410,20 @@
             val userHandle = UserHandle.of(userId)
             val userContext = context.createContextAsUser(userHandle, /* flags= */ 0)
             val intent = Intent(userContext, DesktopWallpaperActivity::class.java)
+            if (
+                desktopWallpaperActivityTokenProvider.getToken(displayId) == null &&
+                    Flags.enablePerDisplayDesktopWallpaperActivity()
+            ) {
+                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
+            }
             intent.putExtra(Intent.EXTRA_USER_HANDLE, userId)
             val options =
                 ActivityOptions.makeBasic().apply {
                     launchWindowingMode = WINDOWING_MODE_FULLSCREEN
                     pendingIntentBackgroundActivityStartMode =
                         ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
-                    if (Flags.enableBugFixesForSecondaryDisplay()) {
+                    if (Flags.enablePerDisplayDesktopWallpaperActivity()) {
                         launchDisplayId = displayId
                     }
                 }
@@ -1414,8 +1440,8 @@
         }
     }
 
-    private fun removeWallpaperActivity(wct: WindowContainerTransaction) {
-        desktopWallpaperActivityTokenProvider.getToken()?.let { token ->
+    private fun removeWallpaperActivity(wct: WindowContainerTransaction, displayId: Int) {
+        desktopWallpaperActivityTokenProvider.getToken(displayId)?.let { token ->
             logV("removeWallpaperActivity")
             if (Flags.enableDesktopWallpaperActivityForSystemUser()) {
                 wct.reorder(token, /* onTop= */ false)
@@ -1440,9 +1466,6 @@
             if (!taskRepository.isOnlyVisibleNonClosingTask(taskId, displayId)) {
                 return
             }
-            if (displayId != DEFAULT_DISPLAY) {
-                return
-            }
         } else if (
             Flags.enableDesktopWindowingPip() &&
                 taskRepository.isMinimizedPipPresentInDisplay(displayId) &&
@@ -1457,7 +1480,7 @@
         desktopModeEnterExitTransitionListener?.onExitDesktopModeTransitionStarted(
             FULLSCREEN_ANIMATION_DURATION
         )
-        removeWallpaperActivity(wct)
+        removeWallpaperActivity(wct, displayId)
     }
 
     fun releaseVisualIndicator() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
index b9a65fe..14c8429 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
@@ -60,7 +60,9 @@
     shellInit: ShellInit,
 ) : Transitions.TransitionObserver {
 
-    private var transitionToCloseWallpaper: IBinder? = null
+    data class CloseWallpaperTransition(val transition: IBinder, val displayId: Int)
+
+    private var transitionToCloseWallpaper: CloseWallpaperTransition? = null
     /* Pending PiP transition and its associated display id and task id. */
     private var pendingPipTransitionAndPipTask: Triple<IBinder, Int, Int>? = null
     private var currentProfileId: Int
@@ -248,9 +250,10 @@
                 desktopRepository.getVisibleTaskCount(taskInfo.displayId) == 0 &&
                     change.mode == TRANSIT_CLOSE &&
                     taskInfo.windowingMode == WINDOWING_MODE_FREEFORM &&
-                    desktopWallpaperActivityTokenProvider.getToken() != null
+                    desktopWallpaperActivityTokenProvider.getToken(taskInfo.displayId) != null
             ) {
-                transitionToCloseWallpaper = transition
+                transitionToCloseWallpaper =
+                    CloseWallpaperTransition(transition, taskInfo.displayId)
                 currentProfileId = taskInfo.userId
             }
         }
@@ -265,25 +268,28 @@
     }
 
     override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {
+        val lastSeenTransitionToCloseWallpaper = transitionToCloseWallpaper
         // TODO: b/332682201 Update repository state
-        if (transitionToCloseWallpaper == transition) {
+        if (lastSeenTransitionToCloseWallpaper?.transition == transition) {
             // TODO: b/362469671 - Handle merging the animation when desktop is also closing.
-            desktopWallpaperActivityTokenProvider.getToken()?.let { wallpaperActivityToken ->
-                if (Flags.enableDesktopWallpaperActivityForSystemUser()) {
-                    transitions.startTransition(
-                        TRANSIT_TO_BACK,
-                        WindowContainerTransaction()
-                            .reorder(wallpaperActivityToken, /* onTop= */ false),
-                        null,
-                    )
-                } else {
-                    transitions.startTransition(
-                        TRANSIT_CLOSE,
-                        WindowContainerTransaction().removeTask(wallpaperActivityToken),
-                        null,
-                    )
+            desktopWallpaperActivityTokenProvider
+                .getToken(lastSeenTransitionToCloseWallpaper.displayId)
+                ?.let { wallpaperActivityToken ->
+                    if (Flags.enableDesktopWallpaperActivityForSystemUser()) {
+                        transitions.startTransition(
+                            TRANSIT_TO_BACK,
+                            WindowContainerTransaction()
+                                .reorder(wallpaperActivityToken, /* onTop= */ false),
+                            null,
+                        )
+                    } else {
+                        transitions.startTransition(
+                            TRANSIT_CLOSE,
+                            WindowContainerTransaction().removeTask(wallpaperActivityToken),
+                            null,
+                        )
+                    }
                 }
-            }
             transitionToCloseWallpaper = null
         } else if (pendingPipTransitionAndPipTask?.first == transition) {
             val desktopRepository = desktopUserRepositories.getProfile(currentProfileId)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index bd676ce..bba778d9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -68,12 +68,12 @@
 import com.android.internal.protolog.ProtoLog;
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.ComponentUtils;
 import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.common.pip.PipBoundsState;
 import com.android.wm.shell.common.pip.PipDisplayLayoutState;
 import com.android.wm.shell.common.pip.PipMenuController;
 import com.android.wm.shell.common.pip.PipUtils;
-import com.android.wm.shell.common.split.SplitScreenUtils;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.shared.TransitionUtil;
 import com.android.wm.shell.shared.pip.PipContentOverlay;
@@ -1359,7 +1359,7 @@
     public boolean isPackageActiveInPip(@Nullable String packageName) {
         final TaskInfo inPipTask = mPipOrganizer.getTaskInfo();
         return packageName != null && inPipTask != null && mPipOrganizer.isInPip()
-                && packageName.equals(SplitScreenUtils.getPackageName(inPipTask.baseIntent));
+                && packageName.equals(ComponentUtils.getPackageName(inPipTask.baseIntent));
     }
 
     private void updatePipForUnhandledTransition(@NonNull TransitionInfo.Change pipChange,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 6e7740d..91150b0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -129,6 +129,20 @@
     }
 
     /**
+     * Called when the Shell wants to start an exit-via-expand from Pip transition/animation.
+     */
+    public void startExpandTransition(WindowContainerTransaction out) {
+        // Default implementation does nothing.
+    }
+
+    /**
+     * Called when the Shell wants to start a remove Pip transition/animation.
+     */
+    public void startRemoveTransition(boolean withFadeout) {
+        // Default implementation does nothing.
+    }
+
+    /**
      * Called when the Shell wants to start resizing Pip transition/animation.
      *
      * @param duration the suggested duration for resize animation.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
index 9babe9e..e22cd37 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
@@ -344,13 +344,22 @@
      */
     @Override
     public void dismissPip() {
+        dismissPip(true /* withFadeout */);
+    }
+
+    /**
+     * Dismisses the pinned stack.
+     *
+     * @param withFadeout should animate with fadeout for the removal
+     */
+    public void dismissPip(boolean withFadeout) {
         if (DEBUG) {
             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                     "%s: removePip: callers=\n%s", TAG, Debug.getCallers(5, "    "));
         }
         cancelPhysicsAnimation();
         mMenuController.hideMenu(ANIM_TYPE_DISMISS, false /* resize */);
-        mPipScheduler.scheduleRemovePip();
+        mPipScheduler.scheduleRemovePip(withFadeout);
     }
 
     /** Sets the movement bounds to use to constrain PIP position animations. */
@@ -473,7 +482,7 @@
                         mPipBoundsState.getMovementBounds().bottom + getBounds().height() * 2,
                         0,
                         mSpringConfig)
-                .withEndActions(this::dismissPip);
+                .withEndActions(() -> dismissPip(false /* withFadeout */));
 
         startBoundsAnimator(
                 getBounds().left /* toX */, getBounds().bottom + getBounds().height() /* toY */);
@@ -772,7 +781,6 @@
             case PipTransitionState.EXITING_PIP:
                 // We need to force finish any local animators if about to leave PiP, to avoid
                 // breaking the state (e.g. leashes are cleaned up upon exit).
-                if (!mPipBoundsState.getMotionBoundsState().isInMotion()) break;
                 cancelPhysicsAnimation();
                 settlePipBoundsAfterPhysicsAnimation(false /* animatingAfter */);
                 break;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index ea8dac9..ed532ca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -19,9 +19,6 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 
-import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
-import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
-
 import android.content.Context;
 import android.graphics.Matrix;
 import android.graphics.Rect;
@@ -112,19 +109,6 @@
         return wct;
     }
 
-    @Nullable
-    private WindowContainerTransaction getRemovePipTransaction() {
-        WindowContainerToken pipTaskToken = mPipTransitionState.getPipTaskToken();
-        if (pipTaskToken == null) {
-            return null;
-        }
-        WindowContainerTransaction wct = new WindowContainerTransaction();
-        wct.setBounds(pipTaskToken, null);
-        wct.setWindowingMode(pipTaskToken, WINDOWING_MODE_UNDEFINED);
-        wct.reorder(pipTaskToken, false);
-        return wct;
-    }
-
     /**
      * Schedules exit PiP via expand transition.
      */
@@ -133,21 +117,16 @@
             if (!mPipTransitionState.isInPip()) return;
             WindowContainerTransaction wct = getExitPipViaExpandTransaction();
             if (wct != null) {
-                mPipTransitionController.startExitTransition(TRANSIT_EXIT_PIP, wct,
-                        null /* destinationBounds */);
+                mPipTransitionController.startExpandTransition(wct);
             }
         });
     }
 
     /** Schedules remove PiP transition. */
-    public void scheduleRemovePip() {
+    public void scheduleRemovePip(boolean withFadeout) {
         mMainExecutor.execute(() -> {
             if (!mPipTransitionState.isInPip()) return;
-            WindowContainerTransaction wct = getRemovePipTransaction();
-            if (wct != null) {
-                mPipTransitionController.startExitTransition(TRANSIT_REMOVE_PIP, wct,
-                        null /* destinationBounds */);
-            }
+            mPipTransitionController.startRemoveTransition(withFadeout);
         });
     }
 
@@ -216,9 +195,11 @@
      * @param degrees the angle to rotate the bounds to.
      */
     public void scheduleUserResizePip(Rect toBounds, float degrees) {
-        if (toBounds.isEmpty()) {
+        if (toBounds.isEmpty() || !mPipTransitionState.isInPip()) {
             ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                    "%s: Attempted to user resize PIP to empty bounds, aborting.", TAG);
+                    "%s: Attempted to user resize PIP in invalid state, aborting;"
+                            + "toBounds=%s, mPipTransitionState=%s",
+                    TAG, toBounds, mPipTransitionState);
             return;
         }
         SurfaceControl leash = mPipTransitionState.getPinnedTaskLeash();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 38015ca..4902455 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -18,6 +18,7 @@
 
 import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.Surface.ROTATION_0;
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
@@ -57,12 +58,12 @@
 import com.android.internal.util.Preconditions;
 import com.android.window.flags.Flags;
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.ComponentUtils;
 import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.common.pip.PipBoundsState;
 import com.android.wm.shell.common.pip.PipDisplayLayoutState;
 import com.android.wm.shell.common.pip.PipMenuController;
 import com.android.wm.shell.common.pip.PipUtils;
-import com.android.wm.shell.common.split.SplitScreenUtils;
 import com.android.wm.shell.desktopmode.DesktopRepository;
 import com.android.wm.shell.desktopmode.DesktopUserRepositories;
 import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider;
@@ -126,6 +127,7 @@
     @Nullable
     private IBinder mResizeTransition;
     private int mBoundsChangeDuration = BOUNDS_CHANGE_JUMPCUT_DURATION;
+    private boolean mPendingRemoveWithFadeout;
 
 
     //
@@ -184,15 +186,19 @@
     //
 
     @Override
-    public void startExitTransition(int type, WindowContainerTransaction out,
-            @Nullable Rect destinationBounds) {
-        if (out == null) {
-            return;
-        }
-        IBinder transition = mTransitions.startTransition(type, out, this);
-        if (type == TRANSIT_EXIT_PIP) {
-            mExitViaExpandTransition = transition;
-        }
+    public void startExpandTransition(WindowContainerTransaction out) {
+        if (out == null) return;
+        mPipTransitionState.setState(PipTransitionState.EXITING_PIP);
+        mExitViaExpandTransition = mTransitions.startTransition(TRANSIT_EXIT_PIP, out, this);
+    }
+
+    @Override
+    public void startRemoveTransition(boolean withFadeout) {
+        final WindowContainerTransaction wct = getRemovePipTransaction();
+        if (wct == null) return;
+        mPipTransitionState.setState(PipTransitionState.EXITING_PIP);
+        mPendingRemoveWithFadeout = withFadeout;
+        mTransitions.startTransition(TRANSIT_REMOVE_PIP, wct, this);
     }
 
     @Override
@@ -284,7 +290,6 @@
                     finishCallback);
         } else if (transition == mExitViaExpandTransition) {
             mExitViaExpandTransition = null;
-            mPipTransitionState.setState(PipTransitionState.EXITING_PIP);
             return startExpandAnimation(info, startTransaction, finishTransaction, finishCallback);
         } else if (transition == mResizeTransition) {
             mResizeTransition = null;
@@ -690,11 +695,19 @@
         TransitionInfo.Change pipChange = getChangeByToken(info,
                 mPipTransitionState.getPipTaskToken());
         mFinishCallback = finishCallback;
-        PipAlphaAnimator animator = new PipAlphaAnimator(mContext, pipChange.getLeash(),
-                startTransaction, PipAlphaAnimator.FADE_OUT);
+
         finishTransaction.setAlpha(pipChange.getLeash(), 0f);
-        animator.setAnimationEndCallback(this::finishTransition);
-        animator.start();
+        if (mPendingRemoveWithFadeout) {
+            PipAlphaAnimator animator = new PipAlphaAnimator(mContext, pipChange.getLeash(),
+                    startTransaction, PipAlphaAnimator.FADE_OUT);
+            animator.setAnimationEndCallback(this::finishTransition);
+            animator.start();
+        } else {
+            // Jumpcut to a faded-out PiP if no fadeout animation was requested.
+            startTransaction.setAlpha(pipChange.getLeash(), 0f);
+            startTransaction.apply();
+            finishTransition();
+        }
         return true;
     }
 
@@ -824,6 +837,19 @@
         return wct;
     }
 
+    @Nullable
+    private WindowContainerTransaction getRemovePipTransaction() {
+        WindowContainerToken pipTaskToken = mPipTransitionState.getPipTaskToken();
+        if (pipTaskToken == null) {
+            return null;
+        }
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        wct.setBounds(pipTaskToken, null);
+        wct.setWindowingMode(pipTaskToken, WINDOWING_MODE_UNDEFINED);
+        wct.reorder(pipTaskToken, false);
+        return wct;
+    }
+
     private boolean isAutoEnterInButtonNavigation(@NonNull TransitionRequestInfo requestInfo) {
         final ActivityManager.RunningTaskInfo pipTask = requestInfo.getPipChange() != null
                 ? requestInfo.getPipChange().getTaskInfo() : null;
@@ -1000,6 +1026,7 @@
                 }
                 mPipTransitionState.setPinnedTaskLeash(null);
                 mPipTransitionState.setPipTaskInfo(null);
+                mPendingRemoveWithFadeout = false;
                 break;
         }
     }
@@ -1008,6 +1035,6 @@
     public boolean isPackageActiveInPip(@Nullable String packageName) {
         final TaskInfo inPipTask = mPipTransitionState.getPipTaskInfo();
         return packageName != null && inPipTask != null && mPipTransitionState.isInPip()
-                && packageName.equals(SplitScreenUtils.getPackageName(inPipTask.baseIntent));
+                && packageName.equals(ComponentUtils.getPackageName(inPipTask.baseIntent));
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
index 6f9f40a..8805cbb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
@@ -27,7 +27,9 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.internal.protolog.ProtoLog;
 import com.android.internal.util.Preconditions;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.shared.annotations.ShellMainThread;
 
 import java.io.PrintWriter;
@@ -201,6 +203,13 @@
             Preconditions.checkArgument(extra != null && !extra.isEmpty(),
                     "No extra bundle for " + stateToString(state) + " state.");
         }
+        if (!shouldTransitionToState(state)) {
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                    "%s: Attempted to transition to an invalid state=%s, while in %s",
+                    TAG, stateToString(state), this);
+            return;
+        }
+
         if (mState != state) {
             final int prevState = mState;
             mState = state;
@@ -374,6 +383,17 @@
         return ++mPrevCustomState;
     }
 
+    private boolean shouldTransitionToState(@TransitionState int newState) {
+        switch (newState) {
+            case SCHEDULED_BOUNDS_CHANGE:
+                // Allow scheduling bounds change only while in PiP, except for if another bounds
+                // change was scheduled but hasn't started playing yet.
+                return isInPip();
+            default:
+                return true;
+        }
+    }
+
     private static String stateToString(int state) {
         switch (state) {
             case UNDEFINED: return "undefined";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationRunner.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationRunner.aidl
index 8cdb8c4..32c79a2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationRunner.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationRunner.aidl
@@ -17,10 +17,9 @@
 package com.android.wm.shell.recents;
 
 import android.graphics.Rect;
-import android.os.Bundle;
 import android.view.RemoteAnimationTarget;
 import android.window.TaskSnapshot;
-import android.window.TransitionInfo;
+import android.os.Bundle;
 
 import com.android.wm.shell.recents.IRecentsAnimationController;
 
@@ -58,8 +57,7 @@
      */
     void onAnimationStart(in IRecentsAnimationController controller,
             in RemoteAnimationTarget[] apps, in RemoteAnimationTarget[] wallpapers,
-            in Rect homeContentInsets, in Rect minimizedHomeBounds, in Bundle extras,
-            in TransitionInfo info) = 2;
+            in Rect homeContentInsets, in Rect minimizedHomeBounds, in Bundle extras) = 2;
 
     /**
      * Called when the task of an activity that has been started while the recents animation
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index d6f9183..975b650 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -103,6 +103,7 @@
     private final RecentTasksImpl mImpl = new RecentTasksImpl();
     private final ActivityTaskManager mActivityTaskManager;
     private final TaskStackTransitionObserver mTaskStackTransitionObserver;
+    private final RecentsShellCommandHandler mRecentsShellCommandHandler;
     private RecentsTransitionHandler mTransitionHandler = null;
     private IRecentTasksListener mListener;
     private final boolean mPcFeatureEnabled;
@@ -167,6 +168,7 @@
         mDesktopUserRepositories = desktopUserRepositories;
         mTaskStackTransitionObserver = taskStackTransitionObserver;
         mMainExecutor = mainExecutor;
+        mRecentsShellCommandHandler = new RecentsShellCommandHandler(this);
         shellInit.addInitCallback(this::onInit, this);
     }
 
@@ -183,6 +185,7 @@
         mShellController.addExternalInterface(IRecentTasks.DESCRIPTOR,
                 this::createExternalInterface, this);
         mShellCommandHandler.addDumpCallback(this::dump, this);
+        mShellCommandHandler.addCommandCallback("recents", mRecentsShellCommandHandler, this);
         mUserId = ActivityManager.getCurrentUser();
         mDesktopUserRepositories.ifPresent(
                 desktopUserRepositories ->
@@ -656,6 +659,11 @@
         return mActivityTaskManager.removeTask(taskId);
     }
 
+    /** Removes all recent tasks that are visible. */
+    public void removeAllVisibleRecentTasks() throws RemoteException {
+        ActivityTaskManager.getService().removeAllVisibleRecentTasks();
+    }
+
     public void dump(@NonNull PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         pw.println(prefix + TAG);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsShellCommandHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsShellCommandHandler.kt
new file mode 100644
index 0000000..f786e07
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsShellCommandHandler.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.wm.shell.recents
+
+import android.os.RemoteException
+import com.android.wm.shell.sysui.ShellCommandHandler.ShellCommandActionHandler
+import java.io.PrintWriter
+
+class RecentsShellCommandHandler(
+    private val recentTasksController: RecentTasksController
+) : ShellCommandActionHandler {
+    override fun onShellCommand(args: Array<out String>, pw: PrintWriter): Boolean {
+        when (args[0]) {
+            "clearAll" -> return runClearAll(pw)
+            else -> {
+                pw.println("Invalid command: " + args[0])
+                return false
+            }
+        }
+    }
+
+    override fun printShellCommandHelp(pw: PrintWriter, prefix: String) {
+        pw.println("${prefix}clearAll")
+        pw.println("$prefix  Clears all visible recent tasks.")
+    }
+
+    private fun runClearAll(pw: PrintWriter): Boolean {
+        try {
+            recentTasksController.removeAllVisibleRecentTasks()
+        } catch (e: RemoteException) {
+            pw.println("Exception while removing visible recent tasks:")
+            e.printStackTrace(pw)
+            return false
+        }
+        return true
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index aeccd86..db582aa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -587,8 +587,7 @@
                 mListener.onAnimationStart(this,
                         apps.toArray(new RemoteAnimationTarget[apps.size()]),
                         new RemoteAnimationTarget[0],
-                        new Rect(0, 0, 0, 0), new Rect(), new Bundle(),
-                        null);
+                        new Rect(0, 0, 0, 0), new Rect(), new Bundle());
                 for (int i = 0; i < mStateListeners.size(); i++) {
                     mStateListeners.get(i).onTransitionStateChanged(TRANSITION_STATE_ANIMATING);
                 }
@@ -819,7 +818,7 @@
                 mListener.onAnimationStart(this,
                         apps.toArray(new RemoteAnimationTarget[apps.size()]),
                         wallpapers.toArray(new RemoteAnimationTarget[wallpapers.size()]),
-                        new Rect(0, 0, 0, 0), new Rect(), b, info);
+                        new Rect(0, 0, 0, 0), new Rect(), b);
                 for (int i = 0; i < mStateListeners.size(); i++) {
                     mStateListeners.get(i).onTransitionStateChanged(TRANSITION_STATE_ANIMATING);
                 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index c724135..9e88a26 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -82,6 +82,7 @@
 import com.android.wm.shell.R;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.ComponentUtils;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
@@ -682,7 +683,7 @@
         final String packageName1 = shortcutInfo.getPackage();
         // NOTE: This doesn't correctly pull out packageName2 if taskId is referring to a task in
         //       recents that hasn't launched and is not being organized
-        final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer);
+        final String packageName2 = ComponentUtils.getPackageName(taskId, mTaskOrganizer);
         final int userId1 = shortcutInfo.getUserId();
         final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer);
         if (samePackage(packageName1, packageName2, userId1, userId2)) {
@@ -727,10 +728,10 @@
             @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
             @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
         Intent fillInIntent = null;
-        final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent);
+        final String packageName1 = ComponentUtils.getPackageName(pendingIntent);
         // NOTE: This doesn't correctly pull out packageName2 if taskId is referring to a task in
         //       recents that hasn't launched and is not being organized
-        final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer);
+        final String packageName2 = ComponentUtils.getPackageName(taskId, mTaskOrganizer);
         final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer);
         boolean setSecondIntentMultipleTask = false;
         if (samePackage(packageName1, packageName2, userId1, userId2)) {
@@ -766,8 +767,8 @@
             InstanceId instanceId) {
         Intent fillInIntent1 = null;
         Intent fillInIntent2 = null;
-        final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent1);
-        final String packageName2 = SplitScreenUtils.getPackageName(pendingIntent2);
+        final String packageName1 = ComponentUtils.getPackageName(pendingIntent1);
+        final String packageName2 = ComponentUtils.getPackageName(pendingIntent2);
         final ActivityOptions activityOptions1 = options1 != null
                 ? ActivityOptions.fromBundle(options1) : ActivityOptions.makeBasic();
         final ActivityOptions activityOptions2 = options2 != null
@@ -835,7 +836,7 @@
         if (fillInIntent == null) fillInIntent = new Intent();
         fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION);
 
-        final String packageName1 = SplitScreenUtils.getPackageName(intent);
+        final String packageName1 = ComponentUtils.getPackageName(intent);
         final String packageName2 = getPackageName(reverseSplitPosition(position), hideTaskToken);
         final int userId2 = getUserId(reverseSplitPosition(position), hideTaskToken);
         final ComponentName component = intent.getIntent().getComponent();
@@ -900,7 +901,7 @@
             }
         }
 
-        return taskInfo != null ? SplitScreenUtils.getPackageName(taskInfo.baseIntent) : null;
+        return taskInfo != null ? ComponentUtils.getPackageName(taskInfo.baseIntent) : null;
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index b6bd879..c9136b4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -131,6 +131,7 @@
 import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.ComponentUtils;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
@@ -140,7 +141,6 @@
 import com.android.wm.shell.common.split.OffscreenTouchZone;
 import com.android.wm.shell.common.split.SplitDecorManager;
 import com.android.wm.shell.common.split.SplitLayout;
-import com.android.wm.shell.common.split.SplitScreenUtils;
 import com.android.wm.shell.common.split.SplitState;
 import com.android.wm.shell.common.split.SplitWindowManager;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
@@ -929,8 +929,8 @@
         for (int taskId : taskIds) {
             ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId);
             if (task != null) {
-                wct.setWindowingMode(task.token, WINDOWING_MODE_UNDEFINED)
-                        .setBounds(task.token, null);
+                wct.setWindowingMode(task.getToken(), WINDOWING_MODE_UNDEFINED)
+                        .setBounds(task.getToken(), null);
             }
         }
     }
@@ -3536,12 +3536,12 @@
         if (mSplitRequest.mActivateTaskId == taskInfo.taskId) {
             return mSplitRequest.mActivatePosition;
         }
-        final String packageName1 = SplitScreenUtils.getPackageName(mSplitRequest.mStartIntent);
-        final String basePackageName = SplitScreenUtils.getPackageName(taskInfo.baseIntent);
+        final String packageName1 = ComponentUtils.getPackageName(mSplitRequest.mStartIntent);
+        final String basePackageName = ComponentUtils.getPackageName(taskInfo.baseIntent);
         if (packageName1 != null && packageName1.equals(basePackageName)) {
             return mSplitRequest.mActivatePosition;
         }
-        final String packageName2 = SplitScreenUtils.getPackageName(mSplitRequest.mStartIntent2);
+        final String packageName2 = ComponentUtils.getPackageName(mSplitRequest.mStartIntent2);
         if (packageName2 != null && packageName2.equals(basePackageName)) {
             return mSplitRequest.mActivatePosition;
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index 81f444b..c5994f8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -633,7 +633,7 @@
 
         private class ShapeIconFactory extends BaseIconFactory {
             protected ShapeIconFactory(Context context, int fillResIconDpi, int iconBitmapSize) {
-                super(context, fillResIconDpi, iconBitmapSize, true /* shapeDetection */);
+                super(context, fillResIconDpi, iconBitmapSize);
             }
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
index 8dd1498..5c7dd07 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
@@ -536,9 +536,12 @@
             }
             return;
         }
+        // Cache it to avoid NPE and make sure to remove it from recents history.
+        // mTaskToken can be cleared in onTaskVanished() when the task is removed.
+        final WindowContainerToken taskToken = mTaskToken;
         mShellExecutor.execute(() -> {
             WindowContainerTransaction wct = new WindowContainerTransaction();
-            wct.removeTask(mTaskToken);
+            wct.removeTask(taskToken);
             mTaskViewTransitions.closeTaskView(wct, this);
         });
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 41c0a11..2177986 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -43,7 +43,7 @@
 import com.android.window.flags.Flags;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
-import com.android.wm.shell.common.split.SplitScreenUtils;
+import com.android.wm.shell.common.ComponentUtils;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
 import com.android.wm.shell.pip.PipTransitionController;
@@ -645,7 +645,7 @@
         // task enter split.
         if (mPipHandler != null) {
             return mPipHandler
-                    .isPackageActiveInPip(SplitScreenUtils.getPackageName(intent.getIntent()));
+                    .isPackageActiveInPip(ComponentUtils.getPackageName(intent.getIntent()));
         }
         return false;
     }
@@ -657,7 +657,7 @@
         // task enter split.
         if (mPipHandler != null) {
             return mPipHandler.isPackageActiveInPip(
-                    SplitScreenUtils.getPackageName(taskId, shellTaskOrganizer));
+                    ComponentUtils.getPackageName(taskId, shellTaskOrganizer));
         }
         return false;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 429e056..67dae28 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -126,8 +126,6 @@
 import com.android.wm.shell.desktopmode.education.AppHandleEducationController;
 import com.android.wm.shell.desktopmode.education.AppToWebEducationController;
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
-import com.android.wm.shell.recents.RecentsTransitionHandler;
-import com.android.wm.shell.recents.RecentsTransitionStateListener;
 import com.android.wm.shell.shared.FocusTransitionListener;
 import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
 import com.android.wm.shell.shared.annotations.ShellMainThread;
@@ -159,10 +157,8 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Optional;
-import java.util.Set;
 import java.util.function.Supplier;
 
 /**
@@ -251,7 +247,6 @@
     private final DesktopModeEventLogger mDesktopModeEventLogger;
     private final DesktopModeUiEventLogger mDesktopModeUiEventLogger;
     private final WindowDecorTaskResourceLoader mTaskResourceLoader;
-    private final RecentsTransitionHandler mRecentsTransitionHandler;
 
     public DesktopModeWindowDecorViewModel(
             Context context,
@@ -287,8 +282,7 @@
             FocusTransitionObserver focusTransitionObserver,
             DesktopModeEventLogger desktopModeEventLogger,
             DesktopModeUiEventLogger desktopModeUiEventLogger,
-            WindowDecorTaskResourceLoader taskResourceLoader,
-            RecentsTransitionHandler recentsTransitionHandler) {
+            WindowDecorTaskResourceLoader taskResourceLoader) {
         this(
                 context,
                 shellExecutor,
@@ -329,8 +323,7 @@
                 focusTransitionObserver,
                 desktopModeEventLogger,
                 desktopModeUiEventLogger,
-                taskResourceLoader,
-                recentsTransitionHandler);
+                taskResourceLoader);
     }
 
     @VisibleForTesting
@@ -374,8 +367,7 @@
             FocusTransitionObserver focusTransitionObserver,
             DesktopModeEventLogger desktopModeEventLogger,
             DesktopModeUiEventLogger desktopModeUiEventLogger,
-            WindowDecorTaskResourceLoader taskResourceLoader,
-            RecentsTransitionHandler recentsTransitionHandler) {
+            WindowDecorTaskResourceLoader taskResourceLoader) {
         mContext = context;
         mMainExecutor = shellExecutor;
         mMainHandler = mainHandler;
@@ -444,7 +436,6 @@
         mDesktopModeEventLogger = desktopModeEventLogger;
         mDesktopModeUiEventLogger = desktopModeUiEventLogger;
         mTaskResourceLoader = taskResourceLoader;
-        mRecentsTransitionHandler = recentsTransitionHandler;
 
         shellInit.addInitCallback(this::onInit, this);
     }
@@ -459,10 +450,6 @@
                 new DesktopModeOnTaskResizeAnimationListener());
         mDesktopTasksController.setOnTaskRepositionAnimationListener(
                 new DesktopModeOnTaskRepositionAnimationListener());
-        if (Flags.enableDesktopRecentsTransitionsCornersBugfix()) {
-            mRecentsTransitionHandler.addTransitionStateListener(
-                    new DesktopModeRecentsTransitionStateListener());
-        }
         mDisplayController.addDisplayChangingController(mOnDisplayChangingListener);
         try {
             mWindowManager.registerSystemGestureExclusionListener(mGestureExclusionListener,
@@ -1872,38 +1859,6 @@
         }
     }
 
-    private class DesktopModeRecentsTransitionStateListener
-            implements RecentsTransitionStateListener {
-        final Set<Integer> mAnimatingTaskIds = new HashSet<>();
-
-        @Override
-        public void onTransitionStateChanged(int state) {
-            switch (state) {
-                case RecentsTransitionStateListener.TRANSITION_STATE_REQUESTED:
-                    for (int n = 0; n < mWindowDecorByTaskId.size(); n++) {
-                        int taskId = mWindowDecorByTaskId.keyAt(n);
-                        mAnimatingTaskIds.add(taskId);
-                        setIsRecentsTransitionRunningForTask(taskId, true);
-                    }
-                    return;
-                case RecentsTransitionStateListener.TRANSITION_STATE_NOT_RUNNING:
-                    // No Recents transition running - clean up window decorations
-                    for (int taskId : mAnimatingTaskIds) {
-                        setIsRecentsTransitionRunningForTask(taskId, false);
-                    }
-                    mAnimatingTaskIds.clear();
-                    return;
-                default:
-            }
-        }
-
-        private void setIsRecentsTransitionRunningForTask(int taskId, boolean isRecentsRunning) {
-            final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
-            if (decoration == null) return;
-            decoration.setIsRecentsTransitionRunning(isRecentsRunning);
-        }
-    }
-
     private class DragEventListenerImpl
             implements DragPositioningCallbackUtility.DragEventListener {
         @Override
@@ -2014,7 +1969,9 @@
                 Supplier<SurfaceControl.Transaction> transactionFactory,
                 Handler handler) {
             final TaskPositioner taskPositioner = DesktopModeStatus.isVeiledResizeEnabled()
-                    ? new VeiledResizeTaskPositioner(
+                    // TODO(b/383632995): Update when the flag is launched.
+                    ? (Flags.enableConnectedDisplaysWindowDrag()
+                        ? new MultiDisplayVeiledResizeTaskPositioner(
                             taskOrganizer,
                             windowDecoration,
                             displayController,
@@ -2022,6 +1979,14 @@
                             transitions,
                             interactionJankMonitor,
                             handler)
+                        : new VeiledResizeTaskPositioner(
+                            taskOrganizer,
+                            windowDecoration,
+                            displayController,
+                            dragEventListener,
+                            transitions,
+                            interactionJankMonitor,
+                            handler))
                     : new FluidResizeTaskPositioner(
                             taskOrganizer,
                             transitions,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 39a989c..4ac8954 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -204,7 +204,6 @@
     private final MultiInstanceHelper mMultiInstanceHelper;
     private final WindowDecorCaptionHandleRepository mWindowDecorCaptionHandleRepository;
     private final DesktopUserRepositories mDesktopUserRepositories;
-    private boolean mIsRecentsTransitionRunning = false;
 
     private Runnable mLoadAppInfoRunnable;
     private Runnable mSetAppInfoRunnable;
@@ -499,7 +498,7 @@
                 applyStartTransactionOnDraw, shouldSetTaskVisibilityPositionAndCrop,
                 mIsStatusBarVisible, mIsKeyguardVisibleAndOccluded, inFullImmersive,
                 mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus,
-                displayExclusionRegion, mIsRecentsTransitionRunning);
+                displayExclusionRegion);
 
         final WindowDecorLinearLayout oldRootView = mResult.mRootView;
         final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
@@ -870,8 +869,7 @@
             boolean inFullImmersiveMode,
             @NonNull InsetsState displayInsetsState,
             boolean hasGlobalFocus,
-            @NonNull Region displayExclusionRegion,
-            boolean shouldIgnoreCornerRadius) {
+            @NonNull Region displayExclusionRegion) {
         final int captionLayoutId = getDesktopModeWindowDecorLayoutId(taskInfo.getWindowingMode());
         final boolean isAppHeader =
                 captionLayoutId == R.layout.desktop_mode_app_header;
@@ -1008,19 +1006,13 @@
         relayoutParams.mWindowDecorConfig = windowDecorConfig;
 
         if (DesktopModeStatus.useRoundedCorners()) {
-            relayoutParams.mCornerRadius = shouldIgnoreCornerRadius ? INVALID_CORNER_RADIUS :
-                    getCornerRadius(context, relayoutParams.mLayoutResId);
+            relayoutParams.mCornerRadius = taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
+                    ? loadDimensionPixelSize(context.getResources(),
+                    R.dimen.desktop_windowing_freeform_rounded_corner_radius)
+                    : INVALID_CORNER_RADIUS;
         }
     }
 
-    private static int getCornerRadius(@NonNull Context context, int layoutResId) {
-        if (layoutResId == R.layout.desktop_mode_app_header) {
-            return loadDimensionPixelSize(context.getResources(),
-                    R.dimen.desktop_windowing_freeform_rounded_corner_radius);
-        }
-        return INVALID_CORNER_RADIUS;
-    }
-
     /**
      * If task has focused window decor, return the caption id of the fullscreen caption size
      * resource. Otherwise, return ID_NULL and caption width be set to task width.
@@ -1748,17 +1740,6 @@
     }
 
     /**
-     * Declares whether a Recents transition is currently active.
-     *
-     * <p> When a Recents transition is active we allow that transition to take ownership of the
-     * corner radius of its task surfaces, so each window decoration should stop updating the corner
-     * radius of its task surface during that time.
-     */
-    void setIsRecentsTransitionRunning(boolean isRecentsTransitionRunning) {
-        mIsRecentsTransitionRunning = isRecentsTransitionRunning;
-    }
-
-    /**
      * Called when there is a {@link MotionEvent#ACTION_HOVER_EXIT} on the maximize window button.
      */
     void onMaximizeButtonHoverExit() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt
new file mode 100644
index 0000000..8dc921c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.windowdecor
+
+import android.graphics.PointF
+import android.graphics.Rect
+import android.os.Handler
+import android.os.IBinder
+import android.os.Looper
+import android.view.Choreographer
+import android.view.Surface
+import android.view.SurfaceControl
+import android.view.WindowManager
+import android.window.TransitionInfo
+import android.window.TransitionRequestInfo
+import android.window.WindowContainerTransaction
+import com.android.internal.jank.Cuj
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.transition.Transitions
+import java.util.concurrent.TimeUnit
+import java.util.function.Supplier
+
+/**
+ * A task positioner that also takes into account resizing a
+ * [com.android.wm.shell.windowdecor.ResizeVeil] and dragging move across multiple displays.
+ * - If the drag is resizing the task, we resize the veil instead.
+ * - If the drag is repositioning, we consider multi-display topology if needed, and update in the
+ *   typical manner.
+ */
+class MultiDisplayVeiledResizeTaskPositioner(
+    private val taskOrganizer: ShellTaskOrganizer,
+    private val desktopWindowDecoration: DesktopModeWindowDecoration,
+    private val displayController: DisplayController,
+    dragEventListener: DragPositioningCallbackUtility.DragEventListener,
+    private val transactionSupplier: Supplier<SurfaceControl.Transaction>,
+    private val transitions: Transitions,
+    private val interactionJankMonitor: InteractionJankMonitor,
+    @ShellMainThread private val handler: Handler,
+) : TaskPositioner, Transitions.TransitionHandler {
+    private val dragEventListeners =
+        mutableListOf<DragPositioningCallbackUtility.DragEventListener>()
+    private val stableBounds = Rect()
+    private val taskBoundsAtDragStart = Rect()
+    private val repositionStartPoint = PointF()
+    private val repositionTaskBounds = Rect()
+    private val isResizing: Boolean
+        get() =
+            (ctrlType and DragPositioningCallback.CTRL_TYPE_TOP) != 0 ||
+                (ctrlType and DragPositioningCallback.CTRL_TYPE_BOTTOM) != 0 ||
+                (ctrlType and DragPositioningCallback.CTRL_TYPE_LEFT) != 0 ||
+                (ctrlType and DragPositioningCallback.CTRL_TYPE_RIGHT) != 0
+
+    @DragPositioningCallback.CtrlType private var ctrlType = 0
+    private var isResizingOrAnimatingResize = false
+    @Surface.Rotation private var rotation = 0
+
+    constructor(
+        taskOrganizer: ShellTaskOrganizer,
+        windowDecoration: DesktopModeWindowDecoration,
+        displayController: DisplayController,
+        dragEventListener: DragPositioningCallbackUtility.DragEventListener,
+        transitions: Transitions,
+        interactionJankMonitor: InteractionJankMonitor,
+        @ShellMainThread handler: Handler,
+    ) : this(
+        taskOrganizer,
+        windowDecoration,
+        displayController,
+        dragEventListener,
+        Supplier<SurfaceControl.Transaction> { SurfaceControl.Transaction() },
+        transitions,
+        interactionJankMonitor,
+        handler,
+    )
+
+    init {
+        dragEventListeners.add(dragEventListener)
+    }
+
+    override fun onDragPositioningStart(ctrlType: Int, displayId: Int, x: Float, y: Float): Rect {
+        this.ctrlType = ctrlType
+        taskBoundsAtDragStart.set(
+            desktopWindowDecoration.mTaskInfo.configuration.windowConfiguration.bounds
+        )
+        repositionStartPoint[x] = y
+        if (isResizing) {
+            // Capture CUJ for re-sizing window in DW mode.
+            interactionJankMonitor.begin(
+                createLongTimeoutJankConfigBuilder(Cuj.CUJ_DESKTOP_MODE_RESIZE_WINDOW)
+            )
+            if (!desktopWindowDecoration.mHasGlobalFocus) {
+                val wct = WindowContainerTransaction()
+                wct.reorder(
+                    desktopWindowDecoration.mTaskInfo.token,
+                    /* onTop= */ true,
+                    /* includingParents= */ true,
+                )
+                taskOrganizer.applyTransaction(wct)
+            }
+        }
+        for (dragEventListener in dragEventListeners) {
+            dragEventListener.onDragStart(desktopWindowDecoration.mTaskInfo.taskId)
+        }
+        repositionTaskBounds.set(taskBoundsAtDragStart)
+        val rotation =
+            desktopWindowDecoration.mTaskInfo.configuration.windowConfiguration.displayRotation
+        if (stableBounds.isEmpty || this.rotation != rotation) {
+            this.rotation = rotation
+            displayController
+                .getDisplayLayout(desktopWindowDecoration.mDisplay.displayId)!!
+                .getStableBounds(stableBounds)
+        }
+        return Rect(repositionTaskBounds)
+    }
+
+    override fun onDragPositioningMove(displayId: Int, x: Float, y: Float): Rect {
+        check(Looper.myLooper() == handler.looper) {
+            "This method must run on the shell main thread."
+        }
+        val delta = DragPositioningCallbackUtility.calculateDelta(x, y, repositionStartPoint)
+        if (
+            isResizing &&
+                DragPositioningCallbackUtility.changeBounds(
+                    ctrlType,
+                    repositionTaskBounds,
+                    taskBoundsAtDragStart,
+                    stableBounds,
+                    delta,
+                    displayController,
+                    desktopWindowDecoration,
+                )
+        ) {
+            if (!isResizingOrAnimatingResize) {
+                for (dragEventListener in dragEventListeners) {
+                    dragEventListener.onDragMove(desktopWindowDecoration.mTaskInfo.taskId)
+                }
+                desktopWindowDecoration.showResizeVeil(repositionTaskBounds)
+                isResizingOrAnimatingResize = true
+            } else {
+                desktopWindowDecoration.updateResizeVeil(repositionTaskBounds)
+            }
+        } else if (ctrlType == DragPositioningCallback.CTRL_TYPE_UNDEFINED) {
+            // Begin window drag CUJ instrumentation only when drag position moves.
+            interactionJankMonitor.begin(
+                createLongTimeoutJankConfigBuilder(Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW)
+            )
+            val t = transactionSupplier.get()
+            DragPositioningCallbackUtility.setPositionOnDrag(
+                desktopWindowDecoration,
+                repositionTaskBounds,
+                taskBoundsAtDragStart,
+                repositionStartPoint,
+                t,
+                x,
+                y,
+            )
+            t.setFrameTimeline(Choreographer.getInstance().vsyncId)
+            t.apply()
+        }
+        return Rect(repositionTaskBounds)
+    }
+
+    override fun onDragPositioningEnd(displayId: Int, x: Float, y: Float): Rect {
+        val delta = DragPositioningCallbackUtility.calculateDelta(x, y, repositionStartPoint)
+        if (isResizing) {
+            if (taskBoundsAtDragStart != repositionTaskBounds) {
+                DragPositioningCallbackUtility.changeBounds(
+                    ctrlType,
+                    repositionTaskBounds,
+                    taskBoundsAtDragStart,
+                    stableBounds,
+                    delta,
+                    displayController,
+                    desktopWindowDecoration,
+                )
+                desktopWindowDecoration.updateResizeVeil(repositionTaskBounds)
+                val wct = WindowContainerTransaction()
+                wct.setBounds(desktopWindowDecoration.mTaskInfo.token, repositionTaskBounds)
+                transitions.startTransition(WindowManager.TRANSIT_CHANGE, wct, this)
+            } else {
+                // If bounds haven't changed, perform necessary veil reset here as startAnimation
+                // won't be called.
+                resetVeilIfVisible()
+            }
+            interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_RESIZE_WINDOW)
+        } else {
+            DragPositioningCallbackUtility.updateTaskBounds(
+                repositionTaskBounds,
+                taskBoundsAtDragStart,
+                repositionStartPoint,
+                x,
+                y,
+            )
+            interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW)
+        }
+
+        ctrlType = DragPositioningCallback.CTRL_TYPE_UNDEFINED
+        taskBoundsAtDragStart.setEmpty()
+        repositionStartPoint[0f] = 0f
+        return Rect(repositionTaskBounds)
+    }
+
+    private fun resetVeilIfVisible() {
+        if (isResizingOrAnimatingResize) {
+            desktopWindowDecoration.hideResizeVeil()
+            isResizingOrAnimatingResize = false
+        }
+    }
+
+    private fun createLongTimeoutJankConfigBuilder(@Cuj.CujType cujType: Int) =
+        InteractionJankMonitor.Configuration.Builder.withSurface(
+                cujType,
+                desktopWindowDecoration.mContext,
+                desktopWindowDecoration.mTaskSurface,
+                handler,
+            )
+            .setTimeout(LONG_CUJ_TIMEOUT_MS)
+
+    override fun startAnimation(
+        transition: IBinder,
+        info: TransitionInfo,
+        startTransaction: SurfaceControl.Transaction,
+        finishTransaction: SurfaceControl.Transaction,
+        finishCallback: Transitions.TransitionFinishCallback,
+    ): Boolean {
+        for (change in info.changes) {
+            val sc = change.leash
+            val endBounds = change.endAbsBounds
+            val endPosition = change.endRelOffset
+            startTransaction
+                .setWindowCrop(sc, endBounds.width(), endBounds.height())
+                .setPosition(sc, endPosition.x.toFloat(), endPosition.y.toFloat())
+            finishTransaction
+                .setWindowCrop(sc, endBounds.width(), endBounds.height())
+                .setPosition(sc, endPosition.x.toFloat(), endPosition.y.toFloat())
+        }
+
+        startTransaction.apply()
+        resetVeilIfVisible()
+        ctrlType = DragPositioningCallback.CTRL_TYPE_UNDEFINED
+        finishCallback.onTransitionFinished(null /* wct */)
+        isResizingOrAnimatingResize = false
+        interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW)
+        return true
+    }
+
+    /**
+     * We should never reach this as this handler's transitions are only started from shell
+     * explicitly.
+     */
+    override fun handleRequest(
+        transition: IBinder,
+        request: TransitionRequestInfo,
+    ): WindowContainerTransaction? {
+        return null
+    }
+
+    override fun isResizingOrAnimating() = isResizingOrAnimatingResize
+
+    override fun addDragEventListener(
+        dragEventListener: DragPositioningCallbackUtility.DragEventListener
+    ) {
+        dragEventListeners.add(dragEventListener)
+    }
+
+    override fun removeDragEventListener(
+        dragEventListener: DragPositioningCallbackUtility.DragEventListener
+    ) {
+        dragEventListeners.remove(dragEventListener)
+    }
+
+    companion object {
+        // Timeout used for resize and drag CUJs, this is longer than the default timeout to avoid
+        // timing out in the middle of a resize or drag action.
+        private val LONG_CUJ_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(/* duration= */ 10L)
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index e011cc0..d2c79d7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -53,7 +53,12 @@
  * {@link com.android.wm.shell.windowdecor.ResizeVeil}.
  * If the drag is resizing the task, we resize the veil instead.
  * If the drag is repositioning, we update in the typical manner.
+ * <p>
+ * @deprecated This class will be replaced by
+ * {@link com.android.wm.shell.windowdecor.MultiDisplayVeiledResizeTaskPositioner}.
+ * TODO(b/383632995): Remove this class after MultiDisplayVeiledResizeTaskPositioner is launched.
  */
+@Deprecated
 public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.TransitionHandler {
     // Timeout used for resize and drag CUJs, this is longer than the default timeout to avoid
     // timing out in the middle of a resize or drag action.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index fa7183ad..5d1bedb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -967,4 +967,4 @@
             return Objects.hash(mToken, mOwner, mFrame, Arrays.hashCode(mBoundingRects), mFlags);
         }
     }
-}
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/EnterDesktopWithDragExistingWindowsTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/EnterDesktopWithDragExistingWindowsTest.kt
new file mode 100644
index 0000000..2b26bbf
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/EnterDesktopWithDragExistingWindowsTest.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.wm.shell.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.scenarios.EnterDesktopWithDragExistingWindows
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/* Functional test for [EnterDesktopWithDragExistingWindows]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+class EnterDesktopWithDragExistingWindowsTest : EnterDesktopWithDragExistingWindows()
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithAppHandleMenuExistingWindows.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithAppHandleMenuExistingWindows.kt
new file mode 100644
index 0000000..2de0830
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithAppHandleMenuExistingWindows.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.wm.shell.scenarios
+
+import android.platform.test.annotations.Postsubmit
+import android.app.Instrumentation
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.helpers.NewTasksAppHelper
+import com.android.window.flags.Flags
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class EnterDesktopWithAppHandleMenuExistingWindows {
+
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val imeApp = ImeAppHelper(instrumentation)
+    private val newTaskApp = NewTasksAppHelper(instrumentation)
+    private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+
+    @Before
+    fun setup() {
+        Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+        testApp.enterDesktopMode(wmHelper, device)
+        imeApp.launchViaIntent(wmHelper)
+        newTaskApp.launchViaIntent(wmHelper)
+        testApp.launchViaIntent(wmHelper)
+        testApp.exitDesktopWithDragToTopDragZone(wmHelper, device)
+    }
+
+    @Test
+    open fun reenterDesktopWithAppHandleMenu() {
+        testApp.enterDesktopModeFromAppHandleMenu(wmHelper, device)
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+        newTaskApp.exit(wmHelper)
+        imeApp.exit(wmHelper)
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDragExistingWindows.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDragExistingWindows.kt
new file mode 100644
index 0000000..814478a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDragExistingWindows.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.wm.shell.scenarios
+
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.flicker.rules.ChangeDisplayOrientationRule
+import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.helpers.NewTasksAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Test Base Class")
+abstract class EnterDesktopWithDragExistingWindows
+constructor(
+    val rotation: Rotation = Rotation.ROTATION_0,
+    isResizeable: Boolean = true,
+    isLandscapeApp: Boolean = true,
+) : DesktopScenarioCustomAppTestBase(isResizeable, isLandscapeApp) {
+
+    @Rule
+    @JvmField
+    val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+    private val imeApp = ImeAppHelper(instrumentation)
+    private val newTaskApp = NewTasksAppHelper(instrumentation)
+
+    @Before
+    fun setup() {
+        Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+        ChangeDisplayOrientationRule.setRotation(rotation)
+        tapl.enableTransientTaskbar(false)
+
+        testApp.enterDesktopMode(wmHelper, device)
+        imeApp.launchViaIntent(wmHelper)
+        newTaskApp.launchViaIntent(wmHelper)
+        testApp.launchViaIntent(wmHelper)
+        testApp.exitDesktopWithDragToTopDragZone(wmHelper, device)
+    }
+
+    @Test
+    open fun reenterDesktopWithDrag() {
+        // By default this method uses drag to desktop
+        testApp.enterDesktopMode(wmHelper, device)
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+        newTaskApp.exit(wmHelper)
+        imeApp.exit(wmHelper)
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/CopyContentInSplit.kt
index ba46542..31d89f9 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/CopyContentInSplit.kt
@@ -24,6 +24,7 @@
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
 import com.android.wm.shell.Utils
+import com.android.wm.shell.flicker.utils.RecentTasksUtils
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
@@ -61,7 +62,6 @@
 
     @After
     fun teardown() {
-        primaryApp.exit(wmHelper)
-        secondaryApp.exit(wmHelper)
+        RecentTasksUtils.clearAllVisibleRecentTasks(instrumentation)
     }
 }
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByDivider.kt
index d774a31..1af6cac 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByDivider.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByDivider.kt
@@ -24,6 +24,7 @@
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
 import com.android.wm.shell.Utils
+import com.android.wm.shell.flicker.utils.RecentTasksUtils
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
@@ -74,7 +75,6 @@
 
     @After
     fun teardown() {
-        primaryApp.exit(wmHelper)
-        secondaryApp.exit(wmHelper)
+        RecentTasksUtils.clearAllVisibleRecentTasks(instrumentation)
     }
 }
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByGoHome.kt
index 5aa1619..8ad8c7b 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DismissSplitScreenByGoHome.kt
@@ -24,6 +24,7 @@
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
 import com.android.wm.shell.Utils
+import com.android.wm.shell.flicker.utils.RecentTasksUtils
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
@@ -60,7 +61,6 @@
 
     @After
     fun teardown() {
-        primaryApp.exit(wmHelper)
-        secondaryApp.exit(wmHelper)
+        RecentTasksUtils.clearAllVisibleRecentTasks(instrumentation)
     }
 }
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DragDividerToResize.kt
index 668f367..da0ace4 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/scenarios/src/com/android/wm/shell/scenarios/DragDividerToResize.kt
@@ -24,6 +24,7 @@
 import androidx.test.uiautomator.UiDevice
 import com.android.launcher3.tapl.LauncherInstrumentation
 import com.android.wm.shell.Utils
+import com.android.wm.shell.flicker.utils.RecentTasksUtils
 import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import org.junit.After
 import org.junit.Before
@@ -61,7 +62,6 @@
 
     @After
     fun teardown() {
-        primaryApp.exit(wmHelper)
-        secondaryApp.exit(wmHelper)
+        RecentTasksUtils.clearAllVisibleRecentTasks(instrumentation)
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/RecentTasksUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/RecentTasksUtils.kt
new file mode 100644
index 0000000..aa262f9
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/RecentTasksUtils.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.wm.shell.flicker.utils
+
+import android.app.Instrumentation
+
+object RecentTasksUtils {
+    fun clearAllVisibleRecentTasks(instrumentation: Instrumentation) {
+        instrumentation.uiAutomation.executeShellCommand(
+            "dumpsys activity service SystemUIService WMShell recents clearAll"
+        )
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 95ed8b4..f549b056 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -601,7 +601,32 @@
     }
 
     @Test
+    @EnableFlags(
+        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+        Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY,
+    )
+    fun showDesktopApps_onSecondaryDisplay_desktopWallpaperEnabled_perDisplayWallpaperEnabled_shouldShowWallpaper() {
+        val homeTask = setUpHomeTask(SECOND_DISPLAY)
+        val task1 = setUpFreeformTask(SECOND_DISPLAY)
+        val task2 = setUpFreeformTask(SECOND_DISPLAY)
+        markTaskHidden(task1)
+        markTaskHidden(task2)
+
+        controller.showDesktopApps(SECOND_DISPLAY, RemoteTransition(TestRemoteTransition()))
+
+        val wct =
+            getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
+        assertThat(wct.hierarchyOps).hasSize(4)
+        // Expect order to be from bottom: home, wallpaperIntent, task1, task2
+        wct.assertReorderAt(index = 0, homeTask)
+        wct.assertPendingIntentAt(index = 1, desktopWallpaperIntent)
+        wct.assertReorderAt(index = 2, task1)
+        wct.assertReorderAt(index = 3, task2)
+    }
+
+    @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+    @DisableFlags(Flags.FLAG_ENABLE_PER_DISPLAY_DESKTOP_WALLPAPER_ACTIVITY)
     fun showDesktopApps_onSecondaryDisplay_desktopWallpaperEnabled_shouldNotShowWallpaper() {
         val homeTask = setUpHomeTask(SECOND_DISPLAY)
         val task1 = setUpFreeformTask(SECOND_DISPLAY)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
index c42f6c3..aef44a4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
@@ -16,14 +16,10 @@
 
 package com.android.wm.shell.pip2.phone;
 
-import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
-import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
-
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.when;
 import static org.mockito.kotlin.MatchersKt.eq;
@@ -124,12 +120,13 @@
                 .setCallsite("PipSchedulerTest")
                 .build();
         when(mMockPipTransitionState.getPinnedTaskLeash()).thenReturn(testLeash);
+        // PiP is in a valid state by default.
+        when(mMockPipTransitionState.isInPip()).thenReturn(true);
     }
 
     @Test
     public void scheduleExitPipViaExpand_nullTaskToken_noop() {
         setNullPipTaskToken();
-        when(mMockPipTransitionState.isInPip()).thenReturn(true);
 
         mPipScheduler.scheduleExitPipViaExpand();
 
@@ -137,14 +134,12 @@
         assertNotNull(mRunnableArgumentCaptor.getValue());
         mRunnableArgumentCaptor.getValue().run();
 
-        verify(mMockPipTransitionController, never())
-                .startExitTransition(eq(TRANSIT_EXIT_PIP), any(), isNull());
+        verify(mMockPipTransitionController, never()).startExpandTransition(any());
     }
 
     @Test
     public void scheduleExitPipViaExpand_exitTransitionCalled() {
         setMockPipTaskToken();
-        when(mMockPipTransitionState.isInPip()).thenReturn(true);
 
         mPipScheduler.scheduleExitPipViaExpand();
 
@@ -152,23 +147,21 @@
         assertNotNull(mRunnableArgumentCaptor.getValue());
         mRunnableArgumentCaptor.getValue().run();
 
-        verify(mMockPipTransitionController, times(1))
-                .startExitTransition(eq(TRANSIT_EXIT_PIP), any(), isNull());
+        verify(mMockPipTransitionController, times(1)).startExpandTransition(any());
     }
 
     @Test
     public void removePipAfterAnimation() {
         setMockPipTaskToken();
-        when(mMockPipTransitionState.isInPip()).thenReturn(true);
 
-        mPipScheduler.scheduleRemovePip();
+        mPipScheduler.scheduleRemovePip(true /* withFadeout */);
 
         verify(mMockMainExecutor, times(1)).execute(mRunnableArgumentCaptor.capture());
         assertNotNull(mRunnableArgumentCaptor.getValue());
         mRunnableArgumentCaptor.getValue().run();
 
         verify(mMockPipTransitionController, times(1))
-                .startExitTransition(eq(TRANSIT_REMOVE_PIP), any(), isNull());
+                .startRemoveTransition(true /* withFadeout */);
     }
 
     @Test
@@ -192,7 +185,6 @@
     @Test
     public void scheduleAnimateResizePip_boundsConfig_setsConfigAtEnd() {
         setMockPipTaskToken();
-        when(mMockPipTransitionState.isInPip()).thenReturn(true);
 
         mPipScheduler.scheduleAnimateResizePip(TEST_BOUNDS, true);
 
@@ -233,7 +225,6 @@
     @Test
     public void scheduleAnimateResizePip_resizeTransition() {
         setMockPipTaskToken();
-        when(mMockPipTransitionState.isInPip()).thenReturn(true);
 
         mPipScheduler.scheduleAnimateResizePip(TEST_BOUNDS, true, TEST_RESIZE_DURATION);
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTransitionStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTransitionStateTest.java
index 571ae93..fa9b590 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTransitionStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTransitionStateTest.java
@@ -110,4 +110,22 @@
         mPipTransitionState.setState(PipTransitionState.ENTERED_PIP);
         mPipTransitionState.removePipTransitionStateChangedListener(mStateChangedListener);
     }
+
+    @Test
+    public void testBoundsChangeState_notInPip() {
+        Bundle extra = new Bundle();
+        extra.putParcelable(EXTRA_ENTRY_KEY, mEmptyParcelable);
+
+        mPipTransitionState.setState(PipTransitionState.UNDEFINED);
+        mPipTransitionState.setState(PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extra);
+        Assert.assertEquals(PipTransitionState.UNDEFINED, mPipTransitionState.getState());
+
+        mPipTransitionState.setState(PipTransitionState.ENTERING_PIP, extra);
+        mPipTransitionState.setState(PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extra);
+        Assert.assertEquals(PipTransitionState.ENTERING_PIP, mPipTransitionState.getState());
+
+        mPipTransitionState.setState(PipTransitionState.EXITING_PIP);
+        mPipTransitionState.setState(PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extra);
+        Assert.assertEquals(PipTransitionState.EXITING_PIP, mPipTransitionState.getState());
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
index ab43119..894d238 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentsTransitionHandlerTest.java
@@ -169,7 +169,7 @@
         final IResultReceiver finishCallback = mock(IResultReceiver.class);
 
         final IBinder transition = startRecentsTransition(/* synthetic= */ true, runner);
-        verify(runner).onAnimationStart(any(), any(), any(), any(), any(), any(), any());
+        verify(runner).onAnimationStart(any(), any(), any(), any(), any(), any());
 
         // Finish and verify no transition remains and that the provided finish callback is called
         mRecentsTransitionHandler.findController(transition).finish(true /* toHome */,
@@ -184,7 +184,7 @@
         final IRecentsAnimationRunner runner = mock(IRecentsAnimationRunner.class);
 
         final IBinder transition = startRecentsTransition(/* synthetic= */ true, runner);
-        verify(runner).onAnimationStart(any(), any(), any(), any(), any(), any(), any());
+        verify(runner).onAnimationStart(any(), any(), any(), any(), any(), any());
 
         mRecentsTransitionHandler.findController(transition).cancel("test");
         mMainExecutor.flushAll();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index 2d0ea5f..7a88ace 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -17,6 +17,7 @@
 package com.android.wm.shell.splitscreen;
 
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_UNDEFINED;
@@ -32,6 +33,8 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.notNull;
 import static org.mockito.Mockito.atLeastOnce;
@@ -50,9 +53,11 @@
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Looper;
 import android.view.SurfaceControl;
 import android.window.RemoteTransition;
+import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
 import androidx.test.annotation.UiThreadTest;
@@ -84,6 +89,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -425,6 +431,30 @@
                 .startFullscreenTransition(any(), any());
     }
 
+
+    @Test
+    public void startTask_ensureWindowingModeCleared() {
+        SplitScreenTransitions splitScreenTransitions =
+                spy(mStageCoordinator.getSplitTransitions());
+        mStageCoordinator.setSplitTransitions(splitScreenTransitions);
+        ArgumentCaptor<WindowContainerTransaction> wctCaptor =
+                ArgumentCaptor.forClass(WindowContainerTransaction.class);
+        int taskId = 18;
+        IBinder binder = mock(IBinder.class);
+        ActivityManager.RunningTaskInfo rti = mock(ActivityManager.RunningTaskInfo.class);
+        WindowContainerToken mockToken = mock(WindowContainerToken.class);
+        when(mockToken.asBinder()).thenReturn(binder);
+        when(rti.getToken()).thenReturn(mockToken);
+        when(mTaskOrganizer.getRunningTaskInfo(taskId)).thenReturn(rti);
+        mStageCoordinator.startTask(taskId, SPLIT_POSITION_TOP_OR_LEFT, null /*options*/,
+                null, SPLIT_INDEX_UNDEFINED);
+        verify(splitScreenTransitions).startEnterTransition(anyInt(),
+                wctCaptor.capture(), any(), any(), anyInt(), anyBoolean());
+
+        int windowingMode = wctCaptor.getValue().getChanges().get(binder).getWindowingMode();
+        assertEquals(windowingMode, WINDOWING_MODE_UNDEFINED);
+    }
+
     private Transitions createTestTransitions() {
         ShellInit shellInit = new ShellInit(mMainExecutor);
         final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 79e9b9c..ffe8e71 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -59,12 +59,11 @@
 import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
 import com.android.window.flags.Flags
 import com.android.wm.shell.R
+import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
 import com.android.wm.shell.desktopmode.DesktopImmersiveController
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
 import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
-import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
-import com.android.wm.shell.recents.RecentsTransitionStateListener
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
 import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
 import com.android.wm.shell.splitscreen.SplitScreenController
@@ -540,8 +539,7 @@
         onLeftSnapClickListenerCaptor.value.invoke()
 
         verify(mockDesktopTasksController, never())
-            .snapToHalfScreen(
-                eq(decor.mTaskInfo), any(), eq(currentBounds), eq(SnapPosition.LEFT),
+            .snapToHalfScreen(eq(decor.mTaskInfo), any(), eq(currentBounds), eq(SnapPosition.LEFT),
                 eq(ResizeTrigger.MAXIMIZE_BUTTON),
                 eq(InputMethod.UNKNOWN_INPUT_METHOD),
                 eq(decor),
@@ -618,12 +616,11 @@
         onRightSnapClickListenerCaptor.value.invoke()
 
         verify(mockDesktopTasksController, never())
-            .snapToHalfScreen(
-                eq(decor.mTaskInfo), any(), eq(currentBounds), eq(SnapPosition.RIGHT),
+            .snapToHalfScreen(eq(decor.mTaskInfo), any(), eq(currentBounds), eq(SnapPosition.RIGHT),
                 eq(ResizeTrigger.MAXIMIZE_BUTTON),
                 eq(InputMethod.UNKNOWN_INPUT_METHOD),
                 eq(decor),
-            )
+        )
     }
 
     @Test
@@ -1226,49 +1223,6 @@
         verify(task2, never()).onExclusionRegionChanged(newRegion)
     }
 
-    @Test
-    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX)
-    fun testRecentsTransitionStateListener_requestedState_setsTransitionRunning() {
-        val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
-        val decoration = setUpMockDecorationForTask(task)
-        onTaskOpening(task, SurfaceControl())
-
-        desktopModeRecentsTransitionStateListener.onTransitionStateChanged(
-            RecentsTransitionStateListener.TRANSITION_STATE_REQUESTED)
-
-        verify(decoration).setIsRecentsTransitionRunning(true)
-    }
-
-    @Test
-    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX)
-    fun testRecentsTransitionStateListener_nonRunningState_setsTransitionNotRunning() {
-        val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
-        val decoration = setUpMockDecorationForTask(task)
-        onTaskOpening(task, SurfaceControl())
-        desktopModeRecentsTransitionStateListener.onTransitionStateChanged(
-            RecentsTransitionStateListener.TRANSITION_STATE_REQUESTED)
-
-        desktopModeRecentsTransitionStateListener.onTransitionStateChanged(
-            RecentsTransitionStateListener.TRANSITION_STATE_NOT_RUNNING)
-
-        verify(decoration).setIsRecentsTransitionRunning(false)
-    }
-
-    @Test
-    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX)
-    fun testRecentsTransitionStateListener_requestedAndAnimating_setsTransitionRunningOnce() {
-        val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
-        val decoration = setUpMockDecorationForTask(task)
-        onTaskOpening(task, SurfaceControl())
-
-        desktopModeRecentsTransitionStateListener.onTransitionStateChanged(
-            RecentsTransitionStateListener.TRANSITION_STATE_REQUESTED)
-        desktopModeRecentsTransitionStateListener.onTransitionStateChanged(
-            RecentsTransitionStateListener.TRANSITION_STATE_ANIMATING)
-
-        verify(decoration, times(1)).setIsRecentsTransitionRunning(true)
-    }
-
     private fun createOpenTaskDecoration(
         @WindowingMode windowingMode: Int,
         taskSurface: SurfaceControl = SurfaceControl(),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
index 8af8285..b5e8ceb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
@@ -40,7 +40,6 @@
 import android.view.WindowInsets.Type.statusBars
 import com.android.dx.mockito.inline.extended.StaticMockitoSession
 import com.android.internal.jank.InteractionJankMonitor
-import com.android.window.flags.Flags
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.ShellTestCase
@@ -66,8 +65,6 @@
 import com.android.wm.shell.desktopmode.education.AppHandleEducationController
 import com.android.wm.shell.desktopmode.education.AppToWebEducationController
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
-import com.android.wm.shell.recents.RecentsTransitionHandler
-import com.android.wm.shell.recents.RecentsTransitionStateListener
 import com.android.wm.shell.splitscreen.SplitScreenController
 import com.android.wm.shell.sysui.ShellCommandHandler
 import com.android.wm.shell.sysui.ShellController
@@ -154,7 +151,6 @@
     protected val mockFocusTransitionObserver = mock<FocusTransitionObserver>()
     protected val mockCaptionHandleRepository = mock<WindowDecorCaptionHandleRepository>()
     protected val mockDesktopRepository: DesktopRepository = mock<DesktopRepository>()
-    protected val mockRecentsTransitionHandler = mock<RecentsTransitionHandler>()
     protected val motionEvent = mock<MotionEvent>()
     val displayLayout = mock<DisplayLayout>()
     protected lateinit var spyContext: TestableContext
@@ -168,7 +164,6 @@
     protected lateinit var mockitoSession: StaticMockitoSession
     protected lateinit var shellInit: ShellInit
     internal lateinit var desktopModeOnInsetsChangedListener: DesktopModeOnInsetsChangedListener
-    protected lateinit var desktopModeRecentsTransitionStateListener: RecentsTransitionStateListener
     protected lateinit var displayChangingListener:
             DisplayChangeController.OnDisplayChangingListener
     internal lateinit var desktopModeOnKeyguardChangedListener: DesktopModeKeyguardChangeListener
@@ -224,8 +219,7 @@
             mockFocusTransitionObserver,
             desktopModeEventLogger,
             mock<DesktopModeUiEventLogger>(),
-            mock<WindowDecorTaskResourceLoader>(),
-            mockRecentsTransitionHandler,
+            mock<WindowDecorTaskResourceLoader>()
         )
         desktopModeWindowDecorViewModel.setSplitScreenController(mockSplitScreenController)
         whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout)
@@ -262,13 +256,6 @@
         verify(displayInsetsController)
             .addGlobalInsetsChangedListener(insetsChangedCaptor.capture())
         desktopModeOnInsetsChangedListener = insetsChangedCaptor.firstValue
-        val recentsTransitionStateListenerCaptor = argumentCaptor<RecentsTransitionStateListener>()
-        if (Flags.enableDesktopRecentsTransitionsCornersBugfix()) {
-            verify(mockRecentsTransitionHandler)
-                .addTransitionStateListener(recentsTransitionStateListenerCaptor.capture())
-            desktopModeRecentsTransitionStateListener =
-                recentsTransitionStateListenerCaptor.firstValue
-        }
         val keyguardChangedCaptor =
             argumentCaptor<DesktopModeKeyguardChangeListener>()
         verify(mockShellController).addKeyguardChangeListener(keyguardChangedCaptor.capture())
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 9ea5fd6..6b02aef 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -169,7 +169,6 @@
     private static final boolean DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED = false;
     private static final boolean DEFAULT_IS_IN_FULL_IMMERSIVE_MODE = false;
     private static final boolean DEFAULT_HAS_GLOBAL_FOCUS = true;
-    private static final boolean DEFAULT_SHOULD_IGNORE_CORNER_RADIUS = false;
 
     @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
 
@@ -397,31 +396,6 @@
     }
 
     @Test
-    public void updateRelayoutParams_shouldIgnoreCornerRadius_roundedCornersNotSet() {
-        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
-        taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
-        fillRoundedCornersResources(/* fillValue= */ 30);
-        RelayoutParams relayoutParams = new RelayoutParams();
-
-        DesktopModeWindowDecoration.updateRelayoutParams(
-                relayoutParams,
-                mTestableContext,
-                taskInfo,
-                mMockSplitScreenController,
-                DEFAULT_APPLY_START_TRANSACTION_ON_DRAW,
-                DEFAULT_SHOULD_SET_TASK_POSITIONING_AND_CROP,
-                DEFAULT_IS_STATUSBAR_VISIBLE,
-                DEFAULT_IS_KEYGUARD_VISIBLE_AND_OCCLUDED,
-                DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
-                new InsetsState(),
-                DEFAULT_HAS_GLOBAL_FOCUS,
-                mExclusionRegion,
-                /* shouldIgnoreCornerRadius= */ true);
-
-        assertThat(relayoutParams.mCornerRadius).isEqualTo(INVALID_CORNER_RADIUS);
-    }
-
-    @Test
     @EnableFlags(Flags.FLAG_ENABLE_APP_HEADER_WITH_TASK_DENSITY)
     public void updateRelayoutParams_appHeader_usesTaskDensity() {
         final int systemDensity = mTestableContext.getOrCreateTestableResources().getResources()
@@ -660,8 +634,7 @@
                 /* inFullImmersiveMode */ true,
                 insetsState,
                 DEFAULT_HAS_GLOBAL_FOCUS,
-                mExclusionRegion,
-                DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
+                mExclusionRegion);
 
         // Takes status bar inset as padding, ignores caption bar inset.
         assertThat(relayoutParams.mCaptionTopPadding).isEqualTo(50);
@@ -686,8 +659,7 @@
                 /* inFullImmersiveMode */ true,
                 new InsetsState(),
                 DEFAULT_HAS_GLOBAL_FOCUS,
-                mExclusionRegion,
-                DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
+                mExclusionRegion);
 
         assertThat(relayoutParams.mIsInsetSource).isFalse();
     }
@@ -711,8 +683,7 @@
                 DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
                 new InsetsState(),
                 DEFAULT_HAS_GLOBAL_FOCUS,
-                mExclusionRegion,
-                DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
+                mExclusionRegion);
 
         // Header is always shown because it's assumed the status bar is always visible.
         assertThat(relayoutParams.mIsCaptionVisible).isTrue();
@@ -736,8 +707,7 @@
                 DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
                 new InsetsState(),
                 DEFAULT_HAS_GLOBAL_FOCUS,
-                mExclusionRegion,
-                DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
+                mExclusionRegion);
 
         assertThat(relayoutParams.mIsCaptionVisible).isTrue();
     }
@@ -760,8 +730,7 @@
                 DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
                 new InsetsState(),
                 DEFAULT_HAS_GLOBAL_FOCUS,
-                mExclusionRegion,
-                DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
+                mExclusionRegion);
 
         assertThat(relayoutParams.mIsCaptionVisible).isFalse();
     }
@@ -784,8 +753,7 @@
                 DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
                 new InsetsState(),
                 DEFAULT_HAS_GLOBAL_FOCUS,
-                mExclusionRegion,
-                DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
+                mExclusionRegion);
 
         assertThat(relayoutParams.mIsCaptionVisible).isFalse();
     }
@@ -809,8 +777,7 @@
                 /* inFullImmersiveMode */ true,
                 new InsetsState(),
                 DEFAULT_HAS_GLOBAL_FOCUS,
-                mExclusionRegion,
-                DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
+                mExclusionRegion);
 
         assertThat(relayoutParams.mIsCaptionVisible).isTrue();
 
@@ -826,8 +793,7 @@
                 /* inFullImmersiveMode */ true,
                 new InsetsState(),
                 DEFAULT_HAS_GLOBAL_FOCUS,
-                mExclusionRegion,
-                DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
+                mExclusionRegion);
 
         assertThat(relayoutParams.mIsCaptionVisible).isFalse();
     }
@@ -851,8 +817,7 @@
                 /* inFullImmersiveMode */ true,
                 new InsetsState(),
                 DEFAULT_HAS_GLOBAL_FOCUS,
-                mExclusionRegion,
-                DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
+                mExclusionRegion);
 
         assertThat(relayoutParams.mIsCaptionVisible).isFalse();
     }
@@ -1515,8 +1480,7 @@
                 DEFAULT_IS_IN_FULL_IMMERSIVE_MODE,
                 new InsetsState(),
                 DEFAULT_HAS_GLOBAL_FOCUS,
-                mExclusionRegion,
-                DEFAULT_SHOULD_IGNORE_CORNER_RADIUS);
+                mExclusionRegion);
     }
 
     private DesktopModeWindowDecoration createWindowDecoration(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt
new file mode 100644
index 0000000..f179cac
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt
@@ -0,0 +1,611 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.windowdecor
+
+import android.app.ActivityManager
+import android.app.WindowConfiguration
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.Point
+import android.graphics.Rect
+import android.os.Handler
+import android.os.IBinder
+import android.os.Looper
+import android.testing.AndroidTestingRunner
+import android.view.Display
+import android.view.Surface.ROTATION_0
+import android.view.Surface.ROTATION_270
+import android.view.Surface.ROTATION_90
+import android.view.SurfaceControl
+import android.view.SurfaceControl.Transaction
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.window.TransitionInfo
+import android.window.WindowContainerToken
+import androidx.test.filters.SmallTest
+import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.transition.Transitions.TransitionFinishCallback
+import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM
+import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT
+import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP
+import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED
+import java.util.function.Supplier
+import junit.framework.Assert
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.argThat
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+/**
+ * Tests for [MultiDisplayVeiledResizeTaskPositioner].
+ *
+ * Build/Install/Run: atest WMShellUnitTests:MultiDisplayVeiledResizeTaskPositionerTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
+
+    @Mock private lateinit var mockShellTaskOrganizer: ShellTaskOrganizer
+    @Mock private lateinit var mockDesktopWindowDecoration: DesktopModeWindowDecoration
+    @Mock
+    private lateinit var mockDragEventListener: DragPositioningCallbackUtility.DragEventListener
+
+    @Mock private lateinit var taskToken: WindowContainerToken
+    @Mock private lateinit var taskBinder: IBinder
+
+    @Mock private lateinit var mockDisplayController: DisplayController
+    @Mock private lateinit var mockDisplayLayout: DisplayLayout
+    @Mock private lateinit var mockDisplay: Display
+    @Mock private lateinit var mockTransactionFactory: Supplier<SurfaceControl.Transaction>
+    @Mock private lateinit var mockTransaction: SurfaceControl.Transaction
+    @Mock private lateinit var mockTransitionBinder: IBinder
+    @Mock private lateinit var mockTransitionInfo: TransitionInfo
+    @Mock private lateinit var mockFinishCallback: TransitionFinishCallback
+    @Mock private lateinit var mockTransitions: Transitions
+    @Mock private lateinit var mockContext: Context
+    @Mock private lateinit var mockResources: Resources
+    @Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
+    private val mainHandler = Handler(Looper.getMainLooper())
+
+    private lateinit var taskPositioner: MultiDisplayVeiledResizeTaskPositioner
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        mockDesktopWindowDecoration.mDisplay = mockDisplay
+        mockDesktopWindowDecoration.mDecorWindowContext = mockContext
+        whenever(mockContext.getResources()).thenReturn(mockResources)
+        whenever(taskToken.asBinder()).thenReturn(taskBinder)
+        whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
+        whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
+        whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
+            if (
+                mockDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration
+                    .displayRotation == ROTATION_90 ||
+                    mockDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration
+                        .displayRotation == ROTATION_270
+            ) {
+                (i.arguments.first() as Rect).set(STABLE_BOUNDS_LANDSCAPE)
+            } else {
+                (i.arguments.first() as Rect).set(STABLE_BOUNDS_PORTRAIT)
+            }
+        }
+        `when`(mockTransactionFactory.get()).thenReturn(mockTransaction)
+        mockDesktopWindowDecoration.mTaskInfo =
+            ActivityManager.RunningTaskInfo().apply {
+                taskId = TASK_ID
+                token = taskToken
+                minWidth = MIN_WIDTH
+                minHeight = MIN_HEIGHT
+                defaultMinSize = DEFAULT_MIN
+                displayId = DISPLAY_ID
+                configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
+                configuration.windowConfiguration.displayRotation = ROTATION_90
+                isResizeable = true
+            }
+        `when`(mockDesktopWindowDecoration.calculateValidDragArea()).thenReturn(VALID_DRAG_AREA)
+        mockDesktopWindowDecoration.mDisplay = mockDisplay
+        whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
+
+        taskPositioner =
+            MultiDisplayVeiledResizeTaskPositioner(
+                mockShellTaskOrganizer,
+                mockDesktopWindowDecoration,
+                mockDisplayController,
+                mockDragEventListener,
+                mockTransactionFactory,
+                mockTransitions,
+                mockInteractionJankMonitor,
+                mainHandler,
+            )
+    }
+
+    @Test
+    fun testDragResize_noMove_doesNotShowResizeVeil() = runOnUiThread {
+        taskPositioner.onDragPositioningStart(
+            CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
+            DISPLAY_ID,
+            STARTING_BOUNDS.left.toFloat(),
+            STARTING_BOUNDS.top.toFloat(),
+        )
+        verify(mockDesktopWindowDecoration, never()).showResizeVeil(STARTING_BOUNDS)
+
+        taskPositioner.onDragPositioningEnd(
+            DISPLAY_ID,
+            STARTING_BOUNDS.left.toFloat(),
+            STARTING_BOUNDS.top.toFloat(),
+        )
+
+        verify(mockTransitions, never())
+            .startTransition(
+                eq(TRANSIT_CHANGE),
+                argThat { wct ->
+                    return@argThat wct.changes.any { (token, change) ->
+                        token == taskBinder &&
+                            (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) !=
+                                0 &&
+                            change.configuration.windowConfiguration.bounds == STARTING_BOUNDS
+                    }
+                },
+                eq(taskPositioner),
+            )
+        verify(mockDesktopWindowDecoration, never()).hideResizeVeil()
+    }
+
+    @Test
+    fun testDragResize_movesTask_doesNotShowResizeVeil() = runOnUiThread {
+        taskPositioner.onDragPositioningStart(
+            CTRL_TYPE_UNDEFINED,
+            DISPLAY_ID,
+            STARTING_BOUNDS.left.toFloat(),
+            STARTING_BOUNDS.top.toFloat(),
+        )
+
+        taskPositioner.onDragPositioningMove(
+            DISPLAY_ID,
+            STARTING_BOUNDS.left.toFloat() + 60,
+            STARTING_BOUNDS.top.toFloat() + 100,
+        )
+        val rectAfterMove = Rect(STARTING_BOUNDS)
+        rectAfterMove.left += 60
+        rectAfterMove.right += 60
+        rectAfterMove.top += 100
+        rectAfterMove.bottom += 100
+        verify(mockTransaction)
+            .setPosition(any(), eq(rectAfterMove.left.toFloat()), eq(rectAfterMove.top.toFloat()))
+
+        val endBounds =
+            taskPositioner.onDragPositioningEnd(
+                DISPLAY_ID,
+                STARTING_BOUNDS.left.toFloat() + 70,
+                STARTING_BOUNDS.top.toFloat() + 20,
+            )
+        val rectAfterEnd = Rect(STARTING_BOUNDS)
+        rectAfterEnd.left += 70
+        rectAfterEnd.right += 70
+        rectAfterEnd.top += 20
+        rectAfterEnd.bottom += 20
+
+        verify(mockDesktopWindowDecoration, never()).showResizeVeil(any())
+        verify(mockDesktopWindowDecoration, never()).hideResizeVeil()
+        Assert.assertEquals(rectAfterEnd, endBounds)
+    }
+
+    @Test
+    fun testDragResize_resize_boundsUpdateOnEnd() = runOnUiThread {
+        taskPositioner.onDragPositioningStart(
+            CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
+            DISPLAY_ID,
+            STARTING_BOUNDS.right.toFloat(),
+            STARTING_BOUNDS.top.toFloat(),
+        )
+
+        taskPositioner.onDragPositioningMove(
+            DISPLAY_ID,
+            STARTING_BOUNDS.right.toFloat() + 10,
+            STARTING_BOUNDS.top.toFloat() + 10,
+        )
+
+        val rectAfterMove = Rect(STARTING_BOUNDS)
+        rectAfterMove.right += 10
+        rectAfterMove.top += 10
+        verify(mockDesktopWindowDecoration).showResizeVeil(rectAfterMove)
+        verify(mockShellTaskOrganizer, never())
+            .applyTransaction(
+                argThat { wct ->
+                    return@argThat wct.changes.any { (token, change) ->
+                        token == taskBinder &&
+                            (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) !=
+                                0 &&
+                            change.configuration.windowConfiguration.bounds == rectAfterMove
+                    }
+                }
+            )
+
+        taskPositioner.onDragPositioningEnd(
+            DISPLAY_ID,
+            STARTING_BOUNDS.right.toFloat() + 20,
+            STARTING_BOUNDS.top.toFloat() + 20,
+        )
+        val rectAfterEnd = Rect(rectAfterMove)
+        rectAfterEnd.right += 10
+        rectAfterEnd.top += 10
+        verify(mockDesktopWindowDecoration).updateResizeVeil(any())
+        verify(mockTransitions)
+            .startTransition(
+                eq(TRANSIT_CHANGE),
+                argThat { wct ->
+                    return@argThat wct.changes.any { (token, change) ->
+                        token == taskBinder &&
+                            (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) !=
+                                0 &&
+                            change.configuration.windowConfiguration.bounds == rectAfterEnd
+                    }
+                },
+                eq(taskPositioner),
+            )
+    }
+
+    @Test
+    fun testDragResize_noEffectiveMove_skipsTransactionOnEnd() = runOnUiThread {
+        taskPositioner.onDragPositioningStart(
+            DISPLAY_ID,
+            CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
+            STARTING_BOUNDS.left.toFloat(),
+            STARTING_BOUNDS.top.toFloat(),
+        )
+
+        taskPositioner.onDragPositioningMove(
+            DISPLAY_ID,
+            STARTING_BOUNDS.left.toFloat(),
+            STARTING_BOUNDS.top.toFloat(),
+        )
+
+        taskPositioner.onDragPositioningEnd(
+            DISPLAY_ID,
+            STARTING_BOUNDS.left.toFloat() + 10,
+            STARTING_BOUNDS.top.toFloat() + 10,
+        )
+
+        verify(mockTransitions, never())
+            .startTransition(
+                eq(TRANSIT_CHANGE),
+                argThat { wct ->
+                    return@argThat wct.changes.any { (token, change) ->
+                        token == taskBinder &&
+                            (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) !=
+                                0 &&
+                            change.configuration.windowConfiguration.bounds == STARTING_BOUNDS
+                    }
+                },
+                eq(taskPositioner),
+            )
+
+        verify(mockShellTaskOrganizer, never())
+            .applyTransaction(
+                argThat { wct ->
+                    return@argThat wct.changes.any { (token, change) ->
+                        token == taskBinder &&
+                            ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) !=
+                                0)
+                    }
+                }
+            )
+    }
+
+    @Test
+    fun testDragResize_drag_setBoundsNotRunIfDragEndsInDisallowedEndArea() = runOnUiThread {
+        taskPositioner.onDragPositioningStart(
+            CTRL_TYPE_UNDEFINED, // drag
+            DISPLAY_ID,
+            STARTING_BOUNDS.left.toFloat(),
+            STARTING_BOUNDS.top.toFloat(),
+        )
+
+        val newX = STARTING_BOUNDS.left.toFloat() + 5
+        val newY = DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT.toFloat() - 1
+        taskPositioner.onDragPositioningMove(DISPLAY_ID, newX, newY)
+
+        taskPositioner.onDragPositioningEnd(DISPLAY_ID, newX, newY)
+
+        verify(mockShellTaskOrganizer, never())
+            .applyTransaction(
+                argThat { wct ->
+                    return@argThat wct.changes.any { (token, change) ->
+                        token == taskBinder &&
+                            ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) !=
+                                0)
+                    }
+                }
+            )
+    }
+
+    @Test
+    fun testDragResize_resize_resizingTaskReorderedToTopWhenNotFocused() = runOnUiThread {
+        mockDesktopWindowDecoration.mHasGlobalFocus = false
+        taskPositioner.onDragPositioningStart(
+            CTRL_TYPE_RIGHT, // Resize right
+            DISPLAY_ID,
+            STARTING_BOUNDS.left.toFloat(),
+            STARTING_BOUNDS.top.toFloat(),
+        )
+
+        // Verify task is reordered to top
+        verify(mockShellTaskOrganizer)
+            .applyTransaction(
+                argThat { wct ->
+                    return@argThat wct.hierarchyOps.any { hierarchyOps ->
+                        hierarchyOps.container == taskBinder && hierarchyOps.toTop
+                    }
+                }
+            )
+    }
+
+    @Test
+    fun testDragResize_resize_resizingTaskNotReorderedToTopWhenFocused() = runOnUiThread {
+        mockDesktopWindowDecoration.mHasGlobalFocus = true
+        taskPositioner.onDragPositioningStart(
+            CTRL_TYPE_RIGHT, // Resize right
+            DISPLAY_ID,
+            STARTING_BOUNDS.left.toFloat(),
+            STARTING_BOUNDS.top.toFloat(),
+        )
+
+        // Verify task is not reordered to top
+        verify(mockShellTaskOrganizer, never())
+            .applyTransaction(
+                argThat { wct ->
+                    return@argThat wct.hierarchyOps.any { hierarchyOps ->
+                        hierarchyOps.container == taskBinder && hierarchyOps.toTop
+                    }
+                }
+            )
+    }
+
+    @Test
+    fun testDragResize_drag_draggedTaskNotReorderedToTop() = runOnUiThread {
+        mockDesktopWindowDecoration.mHasGlobalFocus = false
+        taskPositioner.onDragPositioningStart(
+            CTRL_TYPE_UNDEFINED, // drag
+            DISPLAY_ID,
+            STARTING_BOUNDS.left.toFloat(),
+            STARTING_BOUNDS.top.toFloat(),
+        )
+
+        // Verify task is not reordered to top since task is already brought to top before dragging
+        // begins
+        verify(mockShellTaskOrganizer, never())
+            .applyTransaction(
+                argThat { wct ->
+                    return@argThat wct.hierarchyOps.any { hierarchyOps ->
+                        hierarchyOps.container == taskBinder && hierarchyOps.toTop
+                    }
+                }
+            )
+    }
+
+    @Test
+    fun testDragResize_drag_updatesStableBoundsOnRotate() = runOnUiThread {
+        // Test landscape stable bounds
+        performDrag(
+            STARTING_BOUNDS.right.toFloat(),
+            STARTING_BOUNDS.bottom.toFloat(),
+            STARTING_BOUNDS.right.toFloat() + 2000,
+            STARTING_BOUNDS.bottom.toFloat() + 2000,
+            CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+        )
+        val rectAfterDrag = Rect(STARTING_BOUNDS)
+        rectAfterDrag.right += 2000
+        rectAfterDrag.bottom = STABLE_BOUNDS_LANDSCAPE.bottom
+        // First drag; we should fetch stable bounds.
+        verify(mockDisplayLayout, times(1)).getStableBounds(any())
+        verify(mockTransitions)
+            .startTransition(
+                eq(TRANSIT_CHANGE),
+                argThat { wct ->
+                    return@argThat wct.changes.any { (token, change) ->
+                        token == taskBinder &&
+                            (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) !=
+                                0 &&
+                            change.configuration.windowConfiguration.bounds == rectAfterDrag
+                    }
+                },
+                eq(taskPositioner),
+            )
+        // Drag back to starting bounds.
+        performDrag(
+            STARTING_BOUNDS.right.toFloat() + 2000,
+            STARTING_BOUNDS.bottom.toFloat(),
+            STARTING_BOUNDS.right.toFloat(),
+            STARTING_BOUNDS.bottom.toFloat(),
+            CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+        )
+
+        // Display did not rotate; we should use previous stable bounds
+        verify(mockDisplayLayout, times(1)).getStableBounds(any())
+
+        // Rotate the screen to portrait
+        mockDesktopWindowDecoration.mTaskInfo.apply {
+            configuration.windowConfiguration.displayRotation = ROTATION_0
+        }
+        // Test portrait stable bounds
+        performDrag(
+            STARTING_BOUNDS.right.toFloat(),
+            STARTING_BOUNDS.bottom.toFloat(),
+            STARTING_BOUNDS.right.toFloat() + 2000,
+            STARTING_BOUNDS.bottom.toFloat() + 2000,
+            CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+        )
+        rectAfterDrag.right = STABLE_BOUNDS_PORTRAIT.right
+        rectAfterDrag.bottom = STARTING_BOUNDS.bottom + 2000
+
+        verify(mockTransitions)
+            .startTransition(
+                eq(TRANSIT_CHANGE),
+                argThat { wct ->
+                    return@argThat wct.changes.any { (token, change) ->
+                        token == taskBinder &&
+                            (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) !=
+                                0 &&
+                            change.configuration.windowConfiguration.bounds == rectAfterDrag
+                    }
+                },
+                eq(taskPositioner),
+            )
+        // Display has rotated; we expect a new stable bounds.
+        verify(mockDisplayLayout, times(2)).getStableBounds(any())
+    }
+
+    @Test
+    fun testIsResizingOrAnimatingResizeSet() = runOnUiThread {
+        Assert.assertFalse(taskPositioner.isResizingOrAnimating)
+
+        taskPositioner.onDragPositioningStart(
+            CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
+            DISPLAY_ID,
+            STARTING_BOUNDS.left.toFloat(),
+            STARTING_BOUNDS.top.toFloat(),
+        )
+
+        taskPositioner.onDragPositioningMove(
+            DISPLAY_ID,
+            STARTING_BOUNDS.left.toFloat() - 20,
+            STARTING_BOUNDS.top.toFloat() - 20,
+        )
+
+        // isResizingOrAnimating should be set to true after move during a resize
+        Assert.assertTrue(taskPositioner.isResizingOrAnimating)
+        verify(mockDragEventListener, times(1)).onDragMove(eq(TASK_ID))
+
+        taskPositioner.onDragPositioningEnd(
+            DISPLAY_ID,
+            STARTING_BOUNDS.left.toFloat(),
+            STARTING_BOUNDS.top.toFloat(),
+        )
+
+        // isResizingOrAnimating should be not be set till false until after transition animation
+        Assert.assertTrue(taskPositioner.isResizingOrAnimating)
+    }
+
+    @Test
+    fun testIsResizingOrAnimatingResizeResetAfterStartAnimation() = runOnUiThread {
+        performDrag(
+            STARTING_BOUNDS.left.toFloat(),
+            STARTING_BOUNDS.top.toFloat(),
+            STARTING_BOUNDS.left.toFloat() - 20,
+            STARTING_BOUNDS.top.toFloat() - 20,
+            CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
+        )
+
+        taskPositioner.startAnimation(
+            mockTransitionBinder,
+            mockTransitionInfo,
+            mockTransaction,
+            mockTransaction,
+            mockFinishCallback,
+        )
+
+        // isResizingOrAnimating should be set to false until after transition successfully consumed
+        Assert.assertFalse(taskPositioner.isResizingOrAnimating)
+    }
+
+    @Test
+    fun testStartAnimation_useEndRelOffset() = runOnUiThread {
+        val changeMock = mock(TransitionInfo.Change::class.java)
+        val startTransaction = mock(Transaction::class.java)
+        val finishTransaction = mock(Transaction::class.java)
+        val point = Point(10, 20)
+        val bounds = Rect(1, 2, 3, 4)
+        `when`(changeMock.leash).thenReturn(mock(SurfaceControl::class.java))
+        `when`(changeMock.endRelOffset).thenReturn(point)
+        `when`(changeMock.endAbsBounds).thenReturn(bounds)
+        `when`(mockTransitionInfo.changes).thenReturn(listOf(changeMock))
+        `when`(startTransaction.setWindowCrop(any(), eq(bounds.width()), eq(bounds.height())))
+            .thenReturn(startTransaction)
+        `when`(finishTransaction.setWindowCrop(any(), eq(bounds.width()), eq(bounds.height())))
+            .thenReturn(finishTransaction)
+
+        taskPositioner.startAnimation(
+            mockTransitionBinder,
+            mockTransitionInfo,
+            startTransaction,
+            finishTransaction,
+            mockFinishCallback,
+        )
+
+        verify(startTransaction).setPosition(any(), eq(point.x.toFloat()), eq(point.y.toFloat()))
+        verify(finishTransaction).setPosition(any(), eq(point.x.toFloat()), eq(point.y.toFloat()))
+        verify(changeMock).endRelOffset
+    }
+
+    private fun performDrag(startX: Float, startY: Float, endX: Float, endY: Float, ctrlType: Int) {
+        taskPositioner.onDragPositioningStart(ctrlType, DISPLAY_ID, startX, startY)
+        taskPositioner.onDragPositioningMove(DISPLAY_ID, endX, endY)
+
+        taskPositioner.onDragPositioningEnd(DISPLAY_ID, endX, endY)
+    }
+
+    companion object {
+        private const val TASK_ID = 5
+        private const val MIN_WIDTH = 10
+        private const val MIN_HEIGHT = 10
+        private const val DENSITY_DPI = 20
+        private const val DEFAULT_MIN = 40
+        private const val DISPLAY_ID = 1
+        private const val NAVBAR_HEIGHT = 50
+        private const val CAPTION_HEIGHT = 50
+        private const val DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT = 10
+        private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
+        private val STARTING_BOUNDS = Rect(100, 100, 200, 200)
+        private val STABLE_BOUNDS_LANDSCAPE =
+            Rect(
+                DISPLAY_BOUNDS.left,
+                DISPLAY_BOUNDS.top + CAPTION_HEIGHT,
+                DISPLAY_BOUNDS.right,
+                DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT,
+            )
+        private val STABLE_BOUNDS_PORTRAIT =
+            Rect(
+                DISPLAY_BOUNDS.top,
+                DISPLAY_BOUNDS.left + CAPTION_HEIGHT,
+                DISPLAY_BOUNDS.bottom,
+                DISPLAY_BOUNDS.right - NAVBAR_HEIGHT,
+            )
+        private val VALID_DRAG_AREA =
+            Rect(
+                DISPLAY_BOUNDS.left - 100,
+                STABLE_BOUNDS_LANDSCAPE.top,
+                DISPLAY_BOUNDS.right - 100,
+                DISPLAY_BOUNDS.bottom - 100,
+            )
+    }
+}
diff --git a/location/java/android/location/Geocoder.java b/location/java/android/location/Geocoder.java
index cdde7c6..9746dab 100644
--- a/location/java/android/location/Geocoder.java
+++ b/location/java/android/location/Geocoder.java
@@ -83,8 +83,11 @@
      * succeed.
      */
     public static boolean isPresent() {
-        ILocationManager lm = Objects.requireNonNull(ILocationManager.Stub.asInterface(
-                ServiceManager.getService(Context.LOCATION_SERVICE)));
+        ILocationManager lm = ILocationManager.Stub.asInterface(
+                ServiceManager.getService(Context.LOCATION_SERVICE));
+        if (lm == null) {
+            return false;
+        }
         try {
             return lm.isGeocodeAvailable();
         } catch (RemoteException e) {
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index f42017d..3104f9d 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -503,7 +503,10 @@
 
         String sessionId = sessionInfo.getId();
         synchronized (mSessionLock) {
-            if (mSessionInfos.containsKey(sessionId)) {
+            var mediaStreams = mOngoingMediaStreams.get(sessionId);
+            if (Flags.enableMirroringInMediaRouter2() && mediaStreams != null) {
+                mediaStreams.mSessionInfo = sessionInfo;
+            } else if (mSessionInfos.containsKey(sessionId)) {
                 mSessionInfos.put(sessionId, sessionInfo);
             } else {
                 Log.w(TAG, "notifySessionUpdated: Ignoring unknown session info.");
@@ -836,6 +839,9 @@
         List<RoutingSessionInfo> sessions;
         synchronized (mSessionLock) {
             sessions = new ArrayList<>(mSessionInfos.values());
+            if (Flags.enableMirroringInMediaRouter2()) {
+                mOngoingMediaStreams.values().forEach(it -> sessions.add(it.mSessionInfo));
+            }
         }
 
         try {
@@ -888,7 +894,13 @@
                 Log.w(TAG, description + ": Ignoring empty sessionId from system service.");
                 return false;
             }
-            if (getSessionInfo(sessionId) == null) {
+            boolean idMatchesSystemSession = false;
+            if (Flags.enableMirroringInMediaRouter2()) {
+                synchronized (mSessionLock) {
+                    idMatchesSystemSession = mOngoingMediaStreams.containsKey(sessionId);
+                }
+            }
+            if (!idMatchesSystemSession && getSessionInfo(sessionId) == null) {
                 Log.w(TAG, description + ": Ignoring unknown session from system service. "
                         + "sessionId=" + sessionId);
                 return false;
@@ -1079,8 +1091,8 @@
          *
          * @hide
          */
-        @GuardedBy("MediaRoute2ProviderService.this.mSessionLock")
         @NonNull
+        // Access guarded by mSessionsLock, but it's not convenient to enforce through @GuardedBy.
         private RoutingSessionInfo mSessionInfo;
 
         // TODO: b/380431086: Add the video equivalent.
diff --git a/media/java/android/media/quality/MediaQualityManager.java b/media/java/android/media/quality/MediaQualityManager.java
index 191b938..aeb028c 100644
--- a/media/java/android/media/quality/MediaQualityManager.java
+++ b/media/java/android/media/quality/MediaQualityManager.java
@@ -54,14 +54,17 @@
     private final IMediaQualityManager mService;
     private final Context mContext;
     private final UserHandle mUserHandle;
-    private final Object mLock = new Object();
-    // @GuardedBy("mLock")
+    private final Object mPpLock = new Object();
+    private final Object mSpLock = new Object();
+    private final Object mAbLock = new Object();
+    private final Object mApLock = new Object();
+    // @GuardedBy("mPpLock")
     private final List<PictureProfileCallbackRecord> mPpCallbackRecords = new ArrayList<>();
-    // @GuardedBy("mLock")
+    // @GuardedBy("mSpLock")
     private final List<SoundProfileCallbackRecord> mSpCallbackRecords = new ArrayList<>();
-    // @GuardedBy("mLock")
+    // @GuardedBy("mAbLock")
     private final List<AmbientBacklightCallbackRecord> mAbCallbackRecords = new ArrayList<>();
-    // @GuardedBy("mLock")
+    // @GuardedBy("mApLock")
     private final List<ActiveProcessingPictureListenerRecord> mApListenerRecords =
             new ArrayList<>();
 
@@ -82,7 +85,7 @@
         IPictureProfileCallback ppCallback = new IPictureProfileCallback.Stub() {
             @Override
             public void onPictureProfileAdded(String profileId, PictureProfile profile) {
-                synchronized (mLock) {
+                synchronized (mPpLock) {
                     for (PictureProfileCallbackRecord record : mPpCallbackRecords) {
                         // TODO: filter callback record
                         record.postPictureProfileAdded(profileId, profile);
@@ -91,7 +94,7 @@
             }
             @Override
             public void onPictureProfileUpdated(String profileId, PictureProfile profile) {
-                synchronized (mLock) {
+                synchronized (mPpLock) {
                     for (PictureProfileCallbackRecord record : mPpCallbackRecords) {
                         // TODO: filter callback record
                         record.postPictureProfileUpdated(profileId, profile);
@@ -100,7 +103,7 @@
             }
             @Override
             public void onPictureProfileRemoved(String profileId, PictureProfile profile) {
-                synchronized (mLock) {
+                synchronized (mPpLock) {
                     for (PictureProfileCallbackRecord record : mPpCallbackRecords) {
                         // TODO: filter callback record
                         record.postPictureProfileRemoved(profileId, profile);
@@ -110,7 +113,7 @@
             @Override
             public void onParameterCapabilitiesChanged(
                     String profileId, List<ParameterCapability> caps) {
-                synchronized (mLock) {
+                synchronized (mPpLock) {
                     for (PictureProfileCallbackRecord record : mPpCallbackRecords) {
                         // TODO: filter callback record
                         record.postParameterCapabilitiesChanged(profileId, caps);
@@ -119,7 +122,7 @@
             }
             @Override
             public void onError(String profileId, int err) {
-                synchronized (mLock) {
+                synchronized (mPpLock) {
                     for (PictureProfileCallbackRecord record : mPpCallbackRecords) {
                         // TODO: filter callback record
                         record.postError(profileId, err);
@@ -130,7 +133,7 @@
         ISoundProfileCallback spCallback = new ISoundProfileCallback.Stub() {
             @Override
             public void onSoundProfileAdded(String profileId, SoundProfile profile) {
-                synchronized (mLock) {
+                synchronized (mSpLock) {
                     for (SoundProfileCallbackRecord record : mSpCallbackRecords) {
                         // TODO: filter callback record
                         record.postSoundProfileAdded(profileId, profile);
@@ -139,7 +142,7 @@
             }
             @Override
             public void onSoundProfileUpdated(String profileId, SoundProfile profile) {
-                synchronized (mLock) {
+                synchronized (mSpLock) {
                     for (SoundProfileCallbackRecord record : mSpCallbackRecords) {
                         // TODO: filter callback record
                         record.postSoundProfileUpdated(profileId, profile);
@@ -148,7 +151,7 @@
             }
             @Override
             public void onSoundProfileRemoved(String profileId, SoundProfile profile) {
-                synchronized (mLock) {
+                synchronized (mSpLock) {
                     for (SoundProfileCallbackRecord record : mSpCallbackRecords) {
                         // TODO: filter callback record
                         record.postSoundProfileRemoved(profileId, profile);
@@ -158,7 +161,7 @@
             @Override
             public void onParameterCapabilitiesChanged(
                     String profileId, List<ParameterCapability> caps) {
-                synchronized (mLock) {
+                synchronized (mSpLock) {
                     for (SoundProfileCallbackRecord record : mSpCallbackRecords) {
                         // TODO: filter callback record
                         record.postParameterCapabilitiesChanged(profileId, caps);
@@ -167,7 +170,7 @@
             }
             @Override
             public void onError(String profileId, int err) {
-                synchronized (mLock) {
+                synchronized (mSpLock) {
                     for (SoundProfileCallbackRecord record : mSpCallbackRecords) {
                         // TODO: filter callback record
                         record.postError(profileId, err);
@@ -178,7 +181,7 @@
         IAmbientBacklightCallback abCallback = new IAmbientBacklightCallback.Stub() {
             @Override
             public void onAmbientBacklightEvent(AmbientBacklightEvent event) {
-                synchronized (mLock) {
+                synchronized (mAbLock) {
                     for (AmbientBacklightCallbackRecord record : mAbCallbackRecords) {
                         record.postAmbientBacklightEvent(event);
                     }
@@ -205,7 +208,7 @@
             @NonNull PictureProfileCallback callback) {
         Preconditions.checkNotNull(callback);
         Preconditions.checkNotNull(executor);
-        synchronized (mLock) {
+        synchronized (mPpLock) {
             mPpCallbackRecords.add(new PictureProfileCallbackRecord(callback, executor));
         }
     }
@@ -215,7 +218,7 @@
      */
     public void unregisterPictureProfileCallback(@NonNull final PictureProfileCallback callback) {
         Preconditions.checkNotNull(callback);
-        synchronized (mLock) {
+        synchronized (mPpLock) {
             for (Iterator<PictureProfileCallbackRecord> it = mPpCallbackRecords.iterator();
                     it.hasNext(); ) {
                 PictureProfileCallbackRecord record = it.next();
@@ -416,7 +419,7 @@
             @NonNull SoundProfileCallback callback) {
         Preconditions.checkNotNull(callback);
         Preconditions.checkNotNull(executor);
-        synchronized (mLock) {
+        synchronized (mSpLock) {
             mSpCallbackRecords.add(new SoundProfileCallbackRecord(callback, executor));
         }
     }
@@ -426,7 +429,7 @@
      */
     public void unregisterSoundProfileCallback(@NonNull final SoundProfileCallback callback) {
         Preconditions.checkNotNull(callback);
-        synchronized (mLock) {
+        synchronized (mSpLock) {
             for (Iterator<SoundProfileCallbackRecord> it = mSpCallbackRecords.iterator();
                     it.hasNext(); ) {
                 SoundProfileCallbackRecord record = it.next();
@@ -785,7 +788,7 @@
             @NonNull AmbientBacklightCallback callback) {
         Preconditions.checkNotNull(callback);
         Preconditions.checkNotNull(executor);
-        synchronized (mLock) {
+        synchronized (mAbLock) {
             mAbCallbackRecords.add(new AmbientBacklightCallbackRecord(callback, executor));
         }
     }
@@ -797,7 +800,7 @@
     public void unregisterAmbientBacklightCallback(
             @NonNull final AmbientBacklightCallback callback) {
         Preconditions.checkNotNull(callback);
-        synchronized (mLock) {
+        synchronized (mAbLock) {
             for (Iterator<AmbientBacklightCallbackRecord> it = mAbCallbackRecords.iterator();
                     it.hasNext(); ) {
                 AmbientBacklightCallbackRecord record = it.next();
@@ -1128,7 +1131,7 @@
             @NonNull Consumer<List<ActiveProcessingPicture>> listener) {
         Preconditions.checkNotNull(listener);
         Preconditions.checkNotNull(executor);
-        synchronized (mLock) {
+        synchronized (mApLock) {
             mApListenerRecords.add(
                     new ActiveProcessingPictureListenerRecord(listener, executor, false));
         }
@@ -1147,7 +1150,7 @@
             @NonNull Consumer<List<ActiveProcessingPicture>> listener) {
         Preconditions.checkNotNull(listener);
         Preconditions.checkNotNull(executor);
-        synchronized (mLock) {
+        synchronized (mApLock) {
             mApListenerRecords.add(
                     new ActiveProcessingPictureListenerRecord(listener, executor, true));
         }
@@ -1160,7 +1163,7 @@
     public void removeActiveProcessingPictureListener(
             @NonNull Consumer<List<ActiveProcessingPicture>> listener) {
         Preconditions.checkNotNull(listener);
-        synchronized (mLock) {
+        synchronized (mApLock) {
             for (Iterator<ActiveProcessingPictureListenerRecord> it = mApListenerRecords.iterator();
                     it.hasNext(); ) {
                 ActiveProcessingPictureListenerRecord record = it.next();
diff --git a/media/java/android/media/tv/flags/media_tv.aconfig b/media/java/android/media/tv/flags/media_tv.aconfig
index 3451dfc..28da556 100644
--- a/media/java/android/media/tv/flags/media_tv.aconfig
+++ b/media/java/android/media/tv/flags/media_tv.aconfig
@@ -120,3 +120,13 @@
     description: "Collect physical address from HDMI-CEC messages in metrics"
     bug: "376001043"
 }
+
+flag {
+  name: "tif_extension_standardization_bugfix"
+  namespace: "tv_os"
+  description: "Bug fix flag for standardizing AIDL extension interface of TIS"
+  bug: "389779152"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
\ No newline at end of file
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 2fe069a..bf330da 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -701,6 +701,9 @@
 
     // Protect mFilterClient from being set to null.
     android::Mutex::Autolock autoLock(mLock);
+    if (mFilterClient == nullptr) {
+        return;
+    }
     uint64_t avSharedMemSize = mFilterClient->getAvSharedHandleInfo().size;
     if (mediaEvent.avMemory.fds.size() > 0 || mediaEvent.avDataId != 0 ||
         (dataLength > 0 && (dataLength + offset) < avSharedMemSize)) {
@@ -868,10 +871,18 @@
 void FilterClientCallbackImpl::onFilterEvent(const vector<DemuxFilterEvent> &events) {
     ALOGV("FilterClientCallbackImpl::onFilterEvent");
     JNIEnv *env = AndroidRuntime::getJNIEnv();
+
     ScopedLocalRef<jobjectArray> array(env);
 
     if (!events.empty()) {
         array.reset(env->NewObjectArray(events.size(), mEventClass, nullptr));
+        if (env->IsSameObject(array.get(), nullptr)) {
+            // It can happen when FilterClientCallbackImpl release the resource
+            // in another thread.
+            ALOGE("FilterClientCallbackImpl::onFilterEvent:"
+                  "Unable to create object array of filter events. Ignoring callback.");
+            return;
+        }
     }
 
     for (int i = 0, arraySize = 0; i < events.size(); i++) {
@@ -1070,14 +1081,15 @@
 
 FilterClientCallbackImpl::~FilterClientCallbackImpl() {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
-    {
-        android::Mutex::Autolock autoLock(mLock);
-        if (mFilterObj != nullptr) {
-            env->DeleteWeakGlobalRef(mFilterObj);
-            mFilterObj = nullptr;
-        }
-        mFilterClient = nullptr;
+
+    android::Mutex::Autolock autoLock(mLock);
+
+    if (mFilterObj != nullptr) {
+        env->DeleteWeakGlobalRef(mFilterObj);
+        mFilterObj = nullptr;
     }
+    mFilterClient = nullptr;
+
     env->DeleteGlobalRef(mEventClass);
     env->DeleteGlobalRef(mSectionEventClass);
     env->DeleteGlobalRef(mMediaEventClass);
diff --git a/media/tests/MediaRouter/Android.bp b/media/tests/MediaRouter/Android.bp
index e4f88a6..e63c59d 100644
--- a/media/tests/MediaRouter/Android.bp
+++ b/media/tests/MediaRouter/Android.bp
@@ -9,7 +9,7 @@
 
 android_test {
     name: "mediaroutertest",
-    team: "trendy_team_android_media_solutions",
+    team: "trendy_team_android_media_better_together",
 
     srcs: ["**/*.java"],
 
diff --git a/native/android/TEST_MAPPING b/native/android/TEST_MAPPING
index be84574..456c719 100644
--- a/native/android/TEST_MAPPING
+++ b/native/android/TEST_MAPPING
@@ -16,9 +16,7 @@
     {
        "name": "CtsOsTestCases_cts_performancehintmanagertest",
        "file_patterns": ["performance_hint.cpp"]
-    }
-  ],
-  "postsubmit": [
+    },
     {
       "name": "CtsThermalTestCases",
        "file_patterns": ["thermal.cpp"]
@@ -27,5 +25,19 @@
       "name": "NativeThermalUnitTestCases",
        "file_patterns": ["thermal.cpp"]
     }
+  ],
+  "postsubmit": [
+    {
+       "file_patterns": ["system_health.cpp"],
+       "name": "NativeSystemHealthUnitTestCases"
+    },
+    {
+      "file_patterns": ["system_health.cpp"],
+      "name": "CtsSystemHealthTestCases",
+      "options": [
+        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+        {"exclude-annotation": "org.junit.Ignore"}
+      ]
+    }
   ]
 }
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index b30b779..49cbd71 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -421,6 +421,7 @@
 LIBANDROID_PLATFORM {
   global:
     AThermal_setIThermalServiceForTesting;
+    ASystemHealth_setIHintManagerForTesting;
     APerformanceHint_setIHintManagerForTesting;
     APerformanceHint_sendHint;
     APerformanceHint_getThreadIds;
diff --git a/native/android/system_health.cpp b/native/android/system_health.cpp
index 5c07ac7..1b43e71 100644
--- a/native/android/system_health.cpp
+++ b/native/android/system_health.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#define LOG_TAG "system_health"
+
 #include <aidl/android/hardware/power/CpuHeadroomParams.h>
 #include <aidl/android/hardware/power/GpuHeadroomParams.h>
 #include <aidl/android/os/CpuHeadroomParamsInternal.h>
@@ -23,6 +25,17 @@
 #include <android/system_health.h>
 #include <binder/IServiceManager.h>
 #include <binder/Status.h>
+#include <system_health_private.h>
+
+#include <list>
+#include <map>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <utility>
+
+#include "android-base/thread_annotations.h"
+#include "utils/SystemClock.h"
 
 using namespace android;
 using namespace aidl::android::os;
@@ -55,9 +68,20 @@
     IHintManager::HintManagerClientData mClientData;
 };
 
+static std::shared_ptr<IHintManager>* gIHintManagerForTesting = nullptr;
+static std::shared_ptr<ASystemHealthManager> gSystemHealthManagerForTesting = nullptr;
+
 ASystemHealthManager* ASystemHealthManager::getInstance() {
     static std::once_flag creationFlag;
     static ASystemHealthManager* instance = nullptr;
+    if (gSystemHealthManagerForTesting) {
+        return gSystemHealthManagerForTesting.get();
+    }
+    if (gIHintManagerForTesting) {
+        gSystemHealthManagerForTesting =
+                std::shared_ptr<ASystemHealthManager>(create(*gIHintManagerForTesting));
+        return gSystemHealthManagerForTesting.get();
+    }
     std::call_once(creationFlag, []() { instance = create(nullptr); });
     return instance;
 }
@@ -121,7 +145,8 @@
         }
         return EPIPE;
     }
-    *outHeadroom = res->get<hal::CpuHeadroomResult::Tag::globalHeadroom>();
+    *outHeadroom = res ? res->get<hal::CpuHeadroomResult::Tag::globalHeadroom>()
+                       : std::numeric_limits<float>::quiet_NaN();
     return OK;
 }
 
@@ -155,37 +180,20 @@
         }
         return EPIPE;
     }
-    *outHeadroom = res->get<hal::GpuHeadroomResult::Tag::globalHeadroom>();
+    *outHeadroom = res ? res->get<hal::GpuHeadroomResult::Tag::globalHeadroom>()
+                       : std::numeric_limits<float>::quiet_NaN();
     return OK;
 }
 
 int ASystemHealthManager::getCpuHeadroomMinIntervalMillis(int64_t* outMinIntervalMillis) {
     if (!mClientData.supportInfo.headroom.isCpuSupported) return ENOTSUP;
-    int64_t minIntervalMillis = 0;
-    ::ndk::ScopedAStatus ret = mHintManager->getCpuHeadroomMinIntervalMillis(&minIntervalMillis);
-    if (!ret.isOk()) {
-        ALOGE("ASystemHealth_getCpuHeadroomMinIntervalMillis fails: %s", ret.getMessage());
-        if (ret.getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
-            return ENOTSUP;
-        }
-        return EPIPE;
-    }
-    *outMinIntervalMillis = minIntervalMillis;
+    *outMinIntervalMillis = mClientData.supportInfo.headroom.cpuMinIntervalMillis;
     return OK;
 }
 
 int ASystemHealthManager::getGpuHeadroomMinIntervalMillis(int64_t* outMinIntervalMillis) {
     if (!mClientData.supportInfo.headroom.isGpuSupported) return ENOTSUP;
-    int64_t minIntervalMillis = 0;
-    ::ndk::ScopedAStatus ret = mHintManager->getGpuHeadroomMinIntervalMillis(&minIntervalMillis);
-    if (!ret.isOk()) {
-        ALOGE("ASystemHealth_getGpuHeadroomMinIntervalMillis fails: %s", ret.getMessage());
-        if (ret.getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
-            return ENOTSUP;
-        }
-        return EPIPE;
-    }
-    *outMinIntervalMillis = minIntervalMillis;
+    *outMinIntervalMillis = mClientData.supportInfo.headroom.gpuMinIntervalMillis;
     return OK;
 }
 
@@ -298,7 +306,6 @@
                                 size_t tidsSize) {
     LOG_ALWAYS_FATAL_IF(tids == nullptr, "%s: tids should not be null", __FUNCTION__);
     params->tids.resize(tidsSize);
-    params->tids.clear();
     for (int i = 0; i < (int)tidsSize; ++i) {
         LOG_ALWAYS_FATAL_IF(tids[i] <= 0, "ACpuHeadroomParams_setTids: Invalid non-positive tid %d",
                             tids[i]);
@@ -355,3 +362,10 @@
 void AGpuHeadroomParams_destroy(AGpuHeadroomParams* _Nullable params) {
     delete params;
 }
+
+void ASystemHealth_setIHintManagerForTesting(void* iManager) {
+    if (iManager == nullptr) {
+        gSystemHealthManagerForTesting = nullptr;
+    }
+    gIHintManagerForTesting = static_cast<std::shared_ptr<IHintManager>*>(iManager);
+}
diff --git a/native/android/tests/system_health/Android.bp b/native/android/tests/system_health/Android.bp
new file mode 100644
index 0000000..30aeb77
--- /dev/null
+++ b/native/android/tests/system_health/Android.bp
@@ -0,0 +1,66 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+cc_test {
+    name: "NativeSystemHealthUnitTestCases",
+
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
+
+    srcs: ["NativeSystemHealthUnitTest.cpp"],
+
+    shared_libs: [
+        "libandroid",
+        "libbinder",
+        "libbinder_ndk",
+        "liblog",
+        "libpowermanager",
+        "libutils",
+    ],
+
+    static_libs: [
+        "libbase",
+        "libgmock",
+        "libgtest",
+    ],
+    stl: "c++_shared",
+
+    test_suites: [
+        "device-tests",
+    ],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+
+    header_libs: [
+        "libandroid_headers_private",
+    ],
+}
diff --git a/native/android/tests/system_health/NativeSystemHealthUnitTest.cpp b/native/android/tests/system_health/NativeSystemHealthUnitTest.cpp
new file mode 100644
index 0000000..3f08fc6
--- /dev/null
+++ b/native/android/tests/system_health/NativeSystemHealthUnitTest.cpp
@@ -0,0 +1,231 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "NativeSystemHealthUnitTest"
+
+#include <aidl/android/os/IHintManager.h>
+#include <android/binder_manager.h>
+#include <android/binder_status.h>
+#include <android/system_health.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <system_health_private.h>
+
+#include <memory>
+#include <optional>
+#include <vector>
+
+using namespace std::chrono_literals;
+namespace hal = aidl::android::hardware::power;
+using aidl::android::os::CpuHeadroomParamsInternal;
+using aidl::android::os::GpuHeadroomParamsInternal;
+using aidl::android::os::IHintManager;
+using aidl::android::os::IHintSession;
+using aidl::android::os::SessionCreationConfig;
+using ndk::ScopedAStatus;
+using ndk::SpAIBinder;
+
+using namespace android;
+using namespace testing;
+
+class MockIHintManager : public IHintManager {
+public:
+    MOCK_METHOD(ScopedAStatus, createHintSessionWithConfig,
+                (const SpAIBinder& token, hal::SessionTag tag,
+                 const SessionCreationConfig& creationConfig, hal::SessionConfig* config,
+                 IHintManager::SessionCreationReturn* _aidl_return),
+                (override));
+    MOCK_METHOD(ScopedAStatus, setHintSessionThreads,
+                (const std::shared_ptr<IHintSession>& _, const ::std::vector<int32_t>& tids),
+                (override));
+    MOCK_METHOD(ScopedAStatus, getHintSessionThreadIds,
+                (const std::shared_ptr<IHintSession>& _, ::std::vector<int32_t>* tids), (override));
+    MOCK_METHOD(ScopedAStatus, getSessionChannel,
+                (const ::ndk::SpAIBinder& in_token,
+                 std::optional<hal::ChannelConfig>* _aidl_return),
+                (override));
+    MOCK_METHOD(ScopedAStatus, closeSessionChannel, (), (override));
+    MOCK_METHOD(ScopedAStatus, getCpuHeadroom,
+                (const CpuHeadroomParamsInternal& _,
+                 std::optional<hal::CpuHeadroomResult>* _aidl_return),
+                (override));
+    MOCK_METHOD(ScopedAStatus, getCpuHeadroomMinIntervalMillis, (int64_t*), (override));
+    MOCK_METHOD(ScopedAStatus, getGpuHeadroom,
+                (const GpuHeadroomParamsInternal& _,
+                 std::optional<hal::GpuHeadroomResult>* _aidl_return),
+                (override));
+    MOCK_METHOD(ScopedAStatus, getGpuHeadroomMinIntervalMillis, (int64_t* _aidl_return),
+                (override));
+    MOCK_METHOD(ScopedAStatus, passSessionManagerBinder, (const SpAIBinder& sessionManager));
+    MOCK_METHOD(ScopedAStatus, registerClient,
+                (const std::shared_ptr<aidl::android::os::IHintManager::IHintManagerClient>& _,
+                 aidl::android::os::IHintManager::HintManagerClientData* _aidl_return),
+                (override));
+    MOCK_METHOD(ScopedAStatus, getClientData,
+                (aidl::android::os::IHintManager::HintManagerClientData * _aidl_return),
+                (override));
+    MOCK_METHOD(SpAIBinder, asBinder, (), (override));
+    MOCK_METHOD(bool, isRemote, (), (override));
+};
+
+class NativeSystemHealthUnitTest : public Test {
+public:
+    void SetUp() override {
+        mMockIHintManager = ndk::SharedRefBase::make<NiceMock<MockIHintManager>>();
+        ASystemHealth_setIHintManagerForTesting(&mMockIHintManager);
+        ON_CALL(*mMockIHintManager, getClientData(_))
+                .WillByDefault(
+                        DoAll(SetArgPointee<0>(mClientData), [] { return ScopedAStatus::ok(); }));
+    }
+
+    void TearDown() override {
+        ASystemHealth_setIHintManagerForTesting(nullptr);
+    }
+
+    IHintManager::HintManagerClientData mClientData{
+            .powerHalVersion = 6,
+            .maxCpuHeadroomThreads = 10,
+            .supportInfo{.headroom{
+                    .isCpuSupported = true,
+                    .isGpuSupported = true,
+                    .cpuMinIntervalMillis = 999,
+                    .gpuMinIntervalMillis = 998,
+                    .cpuMinCalculationWindowMillis = 45,
+                    .cpuMaxCalculationWindowMillis = 9999,
+                    .gpuMinCalculationWindowMillis = 46,
+                    .gpuMaxCalculationWindowMillis = 9998,
+            }},
+    };
+
+    std::shared_ptr<NiceMock<MockIHintManager>> mMockIHintManager = nullptr;
+};
+
+TEST_F(NativeSystemHealthUnitTest, headroomParamsValueRange) {
+    int64_t minIntervalMillis = 0;
+    int minCalculationWindowMillis = 0;
+    int maxCalculationWindowMillis = 0;
+    ASSERT_EQ(OK, ASystemHealth_getCpuHeadroomMinIntervalMillis(&minIntervalMillis));
+    ASSERT_EQ(OK,
+              ASystemHealth_getCpuHeadroomCalculationWindowRange(&minCalculationWindowMillis,
+                                                                 &maxCalculationWindowMillis));
+    ASSERT_EQ(minIntervalMillis, mClientData.supportInfo.headroom.cpuMinIntervalMillis);
+    ASSERT_EQ(minCalculationWindowMillis,
+              mClientData.supportInfo.headroom.cpuMinCalculationWindowMillis);
+    ASSERT_EQ(maxCalculationWindowMillis,
+              mClientData.supportInfo.headroom.cpuMaxCalculationWindowMillis);
+
+    ASSERT_EQ(OK, ASystemHealth_getGpuHeadroomMinIntervalMillis(&minIntervalMillis));
+    ASSERT_EQ(OK,
+              ASystemHealth_getGpuHeadroomCalculationWindowRange(&minCalculationWindowMillis,
+                                                                 &maxCalculationWindowMillis));
+    ASSERT_EQ(minIntervalMillis, mClientData.supportInfo.headroom.gpuMinIntervalMillis);
+    ASSERT_EQ(minCalculationWindowMillis,
+              mClientData.supportInfo.headroom.gpuMinCalculationWindowMillis);
+    ASSERT_EQ(maxCalculationWindowMillis,
+              mClientData.supportInfo.headroom.gpuMaxCalculationWindowMillis);
+}
+
+TEST_F(NativeSystemHealthUnitTest, getCpuHeadroom) {
+    CpuHeadroomParamsInternal internalParams1;
+    ACpuHeadroomParams* params2 = ACpuHeadroomParams_create();
+    ACpuHeadroomParams_setCalculationWindowMillis(params2, 200);
+    CpuHeadroomParamsInternal internalParams2;
+    internalParams2.calculationWindowMillis = 200;
+    ACpuHeadroomParams* params3 = ACpuHeadroomParams_create();
+    ACpuHeadroomParams_setCalculationType(params3, ACPU_HEADROOM_CALCULATION_TYPE_AVERAGE);
+    CpuHeadroomParamsInternal internalParams3;
+    internalParams3.calculationType = hal::CpuHeadroomParams::CalculationType::AVERAGE;
+    ACpuHeadroomParams* params4 = ACpuHeadroomParams_create();
+    int tids[3] = {1, 2, 3};
+    ACpuHeadroomParams_setTids(params4, tids, 3);
+    CpuHeadroomParamsInternal internalParams4;
+    internalParams4.tids = {1, 2, 3};
+
+    EXPECT_CALL(*mMockIHintManager, getCpuHeadroom(internalParams1, _))
+            .Times(Exactly(1))
+            .WillOnce(DoAll(SetArgPointee<1>(hal::CpuHeadroomResult::make<
+                                             hal::CpuHeadroomResult::globalHeadroom>(1.0f)),
+                            [] { return ScopedAStatus::ok(); }));
+    EXPECT_CALL(*mMockIHintManager, getCpuHeadroom(internalParams2, _))
+            .Times(Exactly(1))
+            .WillOnce(DoAll(SetArgPointee<1>(hal::CpuHeadroomResult::make<
+                                             hal::CpuHeadroomResult::globalHeadroom>(2.0f)),
+                            [] { return ScopedAStatus::ok(); }));
+    EXPECT_CALL(*mMockIHintManager, getCpuHeadroom(internalParams3, _))
+            .Times(Exactly(1))
+            .WillOnce(DoAll(SetArgPointee<1>(std::nullopt), [] { return ScopedAStatus::ok(); }));
+    EXPECT_CALL(*mMockIHintManager, getCpuHeadroom(internalParams4, _))
+            .Times(Exactly(1))
+            .WillOnce(DoAll(SetArgPointee<1>(hal::CpuHeadroomResult::make<
+                                             hal::CpuHeadroomResult::globalHeadroom>(4.0f)),
+                            [] { return ScopedAStatus::ok(); }));
+
+    float headroom1 = 0.0f;
+    float headroom2 = 0.0f;
+    float headroom3 = 0.0f;
+    float headroom4 = 0.0f;
+    ASSERT_EQ(OK, ASystemHealth_getCpuHeadroom(nullptr, &headroom1));
+    ASSERT_EQ(OK, ASystemHealth_getCpuHeadroom(params2, &headroom2));
+    ASSERT_EQ(OK, ASystemHealth_getCpuHeadroom(params3, &headroom3));
+    ASSERT_EQ(OK, ASystemHealth_getCpuHeadroom(params4, &headroom4));
+    ASSERT_EQ(1.0f, headroom1);
+    ASSERT_EQ(2.0f, headroom2);
+    ASSERT_TRUE(isnan(headroom3));
+    ASSERT_EQ(4.0f, headroom4);
+
+    ACpuHeadroomParams_destroy(params2);
+    ACpuHeadroomParams_destroy(params3);
+    ACpuHeadroomParams_destroy(params4);
+}
+
+TEST_F(NativeSystemHealthUnitTest, getGpuHeadroom) {
+    GpuHeadroomParamsInternal internalParams1;
+    AGpuHeadroomParams* params2 = AGpuHeadroomParams_create();
+    AGpuHeadroomParams_setCalculationWindowMillis(params2, 200);
+    GpuHeadroomParamsInternal internalParams2;
+    internalParams2.calculationWindowMillis = 200;
+    AGpuHeadroomParams* params3 = AGpuHeadroomParams_create();
+    AGpuHeadroomParams_setCalculationType(params3, AGPU_HEADROOM_CALCULATION_TYPE_AVERAGE);
+    GpuHeadroomParamsInternal internalParams3;
+    internalParams3.calculationType = hal::GpuHeadroomParams::CalculationType::AVERAGE;
+
+    EXPECT_CALL(*mMockIHintManager, getGpuHeadroom(internalParams1, _))
+            .Times(Exactly(1))
+            .WillOnce(DoAll(SetArgPointee<1>(hal::GpuHeadroomResult::make<
+                                             hal::GpuHeadroomResult::globalHeadroom>(1.0f)),
+                            [] { return ScopedAStatus::ok(); }));
+    EXPECT_CALL(*mMockIHintManager, getGpuHeadroom(internalParams2, _))
+            .Times(Exactly(1))
+            .WillOnce(DoAll(SetArgPointee<1>(hal::GpuHeadroomResult::make<
+                                             hal::GpuHeadroomResult::globalHeadroom>(2.0f)),
+                            [] { return ScopedAStatus::ok(); }));
+    EXPECT_CALL(*mMockIHintManager, getGpuHeadroom(internalParams3, _))
+            .Times(Exactly(1))
+            .WillOnce(DoAll(SetArgPointee<1>(std::nullopt), [] { return ScopedAStatus::ok(); }));
+
+    float headroom1 = 0.0f;
+    float headroom2 = 0.0f;
+    float headroom3 = 0.0f;
+    ASSERT_EQ(OK, ASystemHealth_getGpuHeadroom(nullptr, &headroom1));
+    ASSERT_EQ(OK, ASystemHealth_getGpuHeadroom(params2, &headroom2));
+    ASSERT_EQ(OK, ASystemHealth_getGpuHeadroom(params3, &headroom3));
+    ASSERT_EQ(1.0f, headroom1);
+    ASSERT_EQ(2.0f, headroom2);
+    ASSERT_TRUE(isnan(headroom3));
+
+    AGpuHeadroomParams_destroy(params2);
+    AGpuHeadroomParams_destroy(params3);
+}
diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp
index 4180710..bc273c2 100644
--- a/native/graphics/jni/Android.bp
+++ b/native/graphics/jni/Android.bp
@@ -102,8 +102,13 @@
     static_libs: ["libarect"],
     fuzz_config: {
         cc: [
+            // Alphabetical order -- assign to skia-android-triage@google.com
+            "danieldilan@google.com",
             "dichenzhang@google.com",
-            "scroggo@google.com",
+            "fmalita@google.com",
+            "jreck@google.com",
+            "nscobie@google.com",
+            "skia-android-triage@google.com",
         ],
         asan_options: [
             "detect_odr_violation=1",
diff --git a/nfc-non-updatable/flags/flags.aconfig b/nfc-non-updatable/flags/flags.aconfig
index 6b14a1e..54ded0c 100644
--- a/nfc-non-updatable/flags/flags.aconfig
+++ b/nfc-non-updatable/flags/flags.aconfig
@@ -197,3 +197,11 @@
     description: "Expose constructor for ApduServiceInfo"
     bug: "380892385"
 }
+
+flag {
+    name: "nfc_hce_latency_events"
+    is_exported: true
+    namespace: "wallet_integration"
+    description: "Enables tracking latency for HCE"
+    bug: "379849603"
+}
diff --git a/packages/CrashRecovery/framework/Android.bp b/packages/CrashRecovery/framework/Android.bp
index 1a3446ec..5dd42bb 100644
--- a/packages/CrashRecovery/framework/Android.bp
+++ b/packages/CrashRecovery/framework/Android.bp
@@ -1,8 +1,8 @@
 filegroup {
     name: "framework-crashrecovery-sources",
     srcs: [
-        "java/**/*.java",
         "java/**/*.aidl",
+        "java/**/*.java",
     ],
     path: "java",
     visibility: [
@@ -12,11 +12,14 @@
 
 java_sdk_library {
     name: "framework-platformcrashrecovery",
-    srcs: [":framework-crashrecovery-sources"],
+    srcs: [
+        ":framework-crashrecovery-module-sources",
+        ":framework-crashrecovery-sources",
+    ],
     defaults: ["framework-non-updatable-unbundled-defaults"],
     permitted_packages: [
-        "android.service.watchdog",
         "android.crashrecovery",
+        "android.service.watchdog",
     ],
     static_libs: ["android.crashrecovery.flags-aconfig-java"],
     aidl: {
diff --git a/packages/CrashRecovery/framework/java/android/service/watchdog/ExplicitHealthCheckService.java b/packages/CrashRecovery/framework/java/android/service/watchdog/ExplicitHealthCheckService.java
deleted file mode 100644
index fdb0fc5..0000000
--- a/packages/CrashRecovery/framework/java/android/service/watchdog/ExplicitHealthCheckService.java
+++ /dev/null
@@ -1,359 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.service.watchdog;
-
-import static android.os.Parcelable.Creator;
-
-import android.annotation.CallbackExecutor;
-import android.annotation.FlaggedApi;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SdkConstant;
-import android.annotation.SuppressLint;
-import android.annotation.SystemApi;
-import android.app.Service;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.crashrecovery.flags.Flags;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.os.RemoteCallback;
-import android.os.RemoteException;
-import android.util.Log;
-
-import com.android.internal.util.Preconditions;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Consumer;
-
-/**
- * A service to provide packages supporting explicit health checks and route checks to these
- * packages on behalf of the package watchdog.
- *
- * <p>To extend this class, you must declare the service in your manifest file with the
- * {@link android.Manifest.permission#BIND_EXPLICIT_HEALTH_CHECK_SERVICE} permission,
- * and include an intent filter with the {@link #SERVICE_INTERFACE} action. In adddition,
- * your implementation must live in
- * {@link PackageManager#getServicesSystemSharedLibraryPackageName()}.
- * For example:</p>
- * <pre>
- *     &lt;service android:name=".FooExplicitHealthCheckService"
- *             android:exported="true"
- *             android:priority="100"
- *             android:permission="android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE"&gt;
- *         &lt;intent-filter&gt;
- *             &lt;action android:name="android.service.watchdog.ExplicitHealthCheckService" /&gt;
- *         &lt;/intent-filter&gt;
- *     &lt;/service&gt;
- * </pre>
- * @hide
- */
-@SystemApi
-public abstract class ExplicitHealthCheckService extends Service {
-
-    private static final String TAG = "ExplicitHealthCheckService";
-
-    /**
-     * {@link Bundle} key for a {@link List} of {@link PackageConfig} value.
-     *
-     * {@hide}
-     */
-    public static final String EXTRA_SUPPORTED_PACKAGES =
-            "android.service.watchdog.extra.supported_packages";
-
-    /**
-     * {@link Bundle} key for a {@link List} of {@link String} value.
-     *
-     * {@hide}
-     */
-    public static final String EXTRA_REQUESTED_PACKAGES =
-            "android.service.watchdog.extra.requested_packages";
-
-    /**
-     * {@link Bundle} key for a {@link String} value.
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_CRASHRECOVERY)
-    public static final String EXTRA_HEALTH_CHECK_PASSED_PACKAGE =
-            "android.service.watchdog.extra.HEALTH_CHECK_PASSED_PACKAGE";
-
-    /**
-     * The Intent action that a service must respond to. Add it to the intent filter of the service
-     * in its manifest.
-     */
-    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
-    public static final String SERVICE_INTERFACE =
-            "android.service.watchdog.ExplicitHealthCheckService";
-
-    /**
-     * The permission that a service must require to ensure that only Android system can bind to it.
-     * If this permission is not enforced in the AndroidManifest of the service, the system will
-     * skip that service.
-     */
-    public static final String BIND_PERMISSION =
-            "android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE";
-
-    private final ExplicitHealthCheckServiceWrapper mWrapper =
-            new ExplicitHealthCheckServiceWrapper();
-
-    /**
-     * Called when the system requests an explicit health check for {@code packageName}.
-     *
-     * <p> When {@code packageName} passes the check, implementors should call
-     * {@link #notifyHealthCheckPassed} to inform the system.
-     *
-     * <p> It could take many hours before a {@code packageName} passes a check and implementors
-     * should never drop requests unless {@link onCancel} is called or the service dies.
-     *
-     * <p> Requests should not be queued and additional calls while expecting a result for
-     * {@code packageName} should have no effect.
-     */
-    public abstract void onRequestHealthCheck(@NonNull String packageName);
-
-    /**
-     * Called when the system cancels the explicit health check request for {@code packageName}.
-     * Should do nothing if there are is no active request for {@code packageName}.
-     */
-    public abstract void onCancelHealthCheck(@NonNull String packageName);
-
-    /**
-     * Called when the system requests for all the packages supporting explicit health checks. The
-     * system may request an explicit health check for any of these packages with
-     * {@link #onRequestHealthCheck}.
-     *
-     * @return all packages supporting explicit health checks
-     */
-    @NonNull public abstract List<PackageConfig> onGetSupportedPackages();
-
-    /**
-     * Called when the system requests for all the packages that it has currently requested
-     * an explicit health check for.
-     *
-     * @return all packages expecting an explicit health check result
-     */
-    @NonNull public abstract List<String> onGetRequestedPackages();
-
-    private final Handler mHandler = Handler.createAsync(Looper.getMainLooper());
-    @Nullable private Consumer<Bundle> mHealthCheckResultCallback;
-    @Nullable private Executor mCallbackExecutor;
-
-    @Override
-    @NonNull
-    public final IBinder onBind(@NonNull Intent intent) {
-        return mWrapper;
-    }
-
-    /**
-     * Sets a callback to be invoked when an explicit health check passes for a package.
-     * <p>
-     * The callback will receive a {@link Bundle} containing the package name that passed the
-     * health check, identified by the key {@link #EXTRA_HEALTH_CHECK_PASSED_PACKAGE}.
-     * <p>
-     * <b>Note:</b> This API is primarily intended for testing purposes. Calling this outside of a
-     * test environment will override the default callback mechanism used to notify the system
-     * about health check results. Use with caution in production code.
-     *
-     * @param executor The executor on which the callback should be invoked. If {@code null}, the
-     *                 callback will be executed on the main thread.
-     * @param callback A callback that receives a {@link Bundle} containing the package name that
-     *                 passed the health check.
-     */
-    @FlaggedApi(Flags.FLAG_ENABLE_CRASHRECOVERY)
-    public final void setHealthCheckPassedCallback(@CallbackExecutor @Nullable Executor executor,
-            @Nullable Consumer<Bundle> callback) {
-        mCallbackExecutor = executor;
-        mHealthCheckResultCallback = callback;
-    }
-
-    private void executeCallback(@NonNull String packageName) {
-        if (mHealthCheckResultCallback != null) {
-            Objects.requireNonNull(packageName,
-                    "Package passing explicit health check must be non-null");
-            Bundle bundle = new Bundle();
-            bundle.putString(EXTRA_HEALTH_CHECK_PASSED_PACKAGE, packageName);
-            mHealthCheckResultCallback.accept(bundle);
-        } else {
-            Log.wtf(TAG, "System missed explicit health check result for " + packageName);
-        }
-    }
-
-    /**
-     * Implementors should call this to notify the system when explicit health check passes
-     * for {@code packageName};
-     */
-    public final void notifyHealthCheckPassed(@NonNull String packageName) {
-        if (mCallbackExecutor != null) {
-            mCallbackExecutor.execute(() -> executeCallback(packageName));
-        } else {
-            mHandler.post(() -> executeCallback(packageName));
-        }
-    }
-
-    /**
-     * A PackageConfig contains a package supporting explicit health checks and the
-     * timeout in {@link System#uptimeMillis} across reboots after which health
-     * check requests from clients are failed.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final class PackageConfig implements Parcelable {
-        private static final long DEFAULT_HEALTH_CHECK_TIMEOUT_MILLIS = TimeUnit.HOURS.toMillis(1);
-
-        private final String mPackageName;
-        private final long mHealthCheckTimeoutMillis;
-
-        /**
-         * Creates a new instance.
-         *
-         * @param packageName the package name
-         * @param durationMillis the duration in milliseconds, must be greater than or
-         * equal to 0. If it is 0, it will use a system default value.
-         */
-        public PackageConfig(@NonNull String packageName, long healthCheckTimeoutMillis) {
-            mPackageName = Preconditions.checkNotNull(packageName);
-            if (healthCheckTimeoutMillis == 0) {
-                mHealthCheckTimeoutMillis = DEFAULT_HEALTH_CHECK_TIMEOUT_MILLIS;
-            } else {
-                mHealthCheckTimeoutMillis = Preconditions.checkArgumentNonnegative(
-                        healthCheckTimeoutMillis);
-            }
-        }
-
-        private PackageConfig(Parcel parcel) {
-            mPackageName = parcel.readString();
-            mHealthCheckTimeoutMillis = parcel.readLong();
-        }
-
-        /**
-         * Gets the package name.
-         *
-         * @return the package name
-         */
-        public @NonNull String getPackageName() {
-            return mPackageName;
-        }
-
-        /**
-         * Gets the timeout in milliseconds to evaluate an explicit health check result after a
-         * request.
-         *
-         * @return the duration in {@link System#uptimeMillis} across reboots
-         */
-        public long getHealthCheckTimeoutMillis() {
-            return mHealthCheckTimeoutMillis;
-        }
-
-        @NonNull
-        @Override
-        public String toString() {
-            return "PackageConfig{" + mPackageName + ", " + mHealthCheckTimeoutMillis + "}";
-        }
-
-        @Override
-        public boolean equals(@Nullable Object other) {
-            if (other == this) {
-                return true;
-            }
-            if (!(other instanceof PackageConfig)) {
-                return false;
-            }
-
-            PackageConfig otherInfo = (PackageConfig) other;
-            return Objects.equals(otherInfo.getHealthCheckTimeoutMillis(),
-                    mHealthCheckTimeoutMillis)
-                    && Objects.equals(otherInfo.getPackageName(), mPackageName);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(mPackageName, mHealthCheckTimeoutMillis);
-        }
-
-        @Override
-        public int describeContents() {
-            return 0;
-        }
-
-        @Override
-        public void writeToParcel(@SuppressLint({"MissingNullability"}) Parcel parcel, int flags) {
-            parcel.writeString(mPackageName);
-            parcel.writeLong(mHealthCheckTimeoutMillis);
-        }
-
-        public static final @NonNull Creator<PackageConfig> CREATOR = new Creator<PackageConfig>() {
-                @Override
-                public PackageConfig createFromParcel(Parcel source) {
-                    return new PackageConfig(source);
-                }
-
-                @Override
-                public PackageConfig[] newArray(int size) {
-                    return new PackageConfig[size];
-                }
-            };
-    }
-
-
-    private class ExplicitHealthCheckServiceWrapper extends IExplicitHealthCheckService.Stub {
-        @Override
-        public void setCallback(RemoteCallback callback) throws RemoteException {
-            mHandler.post(() -> mHealthCheckResultCallback = callback::sendResult);
-        }
-
-        @Override
-        public void request(String packageName) throws RemoteException {
-            mHandler.post(() -> ExplicitHealthCheckService.this.onRequestHealthCheck(packageName));
-        }
-
-        @Override
-        public void cancel(String packageName) throws RemoteException {
-            mHandler.post(() -> ExplicitHealthCheckService.this.onCancelHealthCheck(packageName));
-        }
-
-        @Override
-        public void getSupportedPackages(RemoteCallback callback) throws RemoteException {
-            mHandler.post(() -> {
-                List<PackageConfig> packages =
-                        ExplicitHealthCheckService.this.onGetSupportedPackages();
-                Objects.requireNonNull(packages, "Supported package list must be non-null");
-                Bundle bundle = new Bundle();
-                bundle.putParcelableArrayList(EXTRA_SUPPORTED_PACKAGES, new ArrayList<>(packages));
-                callback.sendResult(bundle);
-            });
-        }
-
-        @Override
-        public void getRequestedPackages(RemoteCallback callback) throws RemoteException {
-            mHandler.post(() -> {
-                List<String> packages =
-                        ExplicitHealthCheckService.this.onGetRequestedPackages();
-                Objects.requireNonNull(packages, "Requested  package list must be non-null");
-                Bundle bundle = new Bundle();
-                bundle.putStringArrayList(EXTRA_REQUESTED_PACKAGES, new ArrayList<>(packages));
-                callback.sendResult(bundle);
-            });
-        }
-    }
-}
diff --git a/packages/CrashRecovery/framework/java/android/service/watchdog/IExplicitHealthCheckService.aidl b/packages/CrashRecovery/framework/java/android/service/watchdog/IExplicitHealthCheckService.aidl
deleted file mode 100644
index 9096509..0000000
--- a/packages/CrashRecovery/framework/java/android/service/watchdog/IExplicitHealthCheckService.aidl
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.service.watchdog;
-
-import android.os.RemoteCallback;
-
-/**
- * @hide
- */
-@PermissionManuallyEnforced
-oneway interface IExplicitHealthCheckService
-{
-    void setCallback(in @nullable RemoteCallback callback);
-    void request(String packageName);
-    void cancel(String packageName);
-    void getSupportedPackages(in RemoteCallback callback);
-    void getRequestedPackages(in RemoteCallback callback);
-}
diff --git a/packages/CrashRecovery/framework/java/android/service/watchdog/OWNERS b/packages/CrashRecovery/framework/java/android/service/watchdog/OWNERS
deleted file mode 100644
index 1c045e1..0000000
--- a/packages/CrashRecovery/framework/java/android/service/watchdog/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-narayan@google.com
-nandana@google.com
-olilan@google.com
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/ExplicitHealthCheckController.java b/packages/CrashRecovery/services/module/java/com/android/server/ExplicitHealthCheckController.java
deleted file mode 100644
index da9a139..0000000
--- a/packages/CrashRecovery/services/module/java/com/android/server/ExplicitHealthCheckController.java
+++ /dev/null
@@ -1,447 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_HEALTH_CHECK_PASSED_PACKAGE;
-import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_REQUESTED_PACKAGES;
-import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_SUPPORTED_PACKAGES;
-import static android.service.watchdog.ExplicitHealthCheckService.PackageConfig;
-
-import android.Manifest;
-import android.annotation.MainThread;
-import android.annotation.Nullable;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.os.IBinder;
-import android.os.RemoteCallback;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.service.watchdog.ExplicitHealthCheckService;
-import android.service.watchdog.IExplicitHealthCheckService;
-import android.text.TextUtils;
-import android.util.ArraySet;
-import android.util.Slog;
-
-import com.android.internal.annotations.GuardedBy;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-import java.util.function.Consumer;
-
-// TODO(b/120598832): Add tests
-/**
- * Controls the connections with {@link ExplicitHealthCheckService}.
- */
-class ExplicitHealthCheckController {
-    private static final String TAG = "ExplicitHealthCheckController";
-    private final Object mLock = new Object();
-    private final Context mContext;
-
-    // Called everytime a package passes the health check, so the watchdog is notified of the
-    // passing check. In practice, should never be null after it has been #setEnabled.
-    // To prevent deadlocks between the controller and watchdog threads, we have
-    // a lock invariant to ALWAYS acquire the PackageWatchdog#mLock before #mLock in this class.
-    // It's easier to just NOT hold #mLock when calling into watchdog code on this consumer.
-    @GuardedBy("mLock") @Nullable private Consumer<String> mPassedConsumer;
-    // Called everytime after a successful #syncRequest call, so the watchdog can receive packages
-    // supporting health checks and update its internal state. In practice, should never be null
-    // after it has been #setEnabled.
-    // To prevent deadlocks between the controller and watchdog threads, we have
-    // a lock invariant to ALWAYS acquire the PackageWatchdog#mLock before #mLock in this class.
-    // It's easier to just NOT hold #mLock when calling into watchdog code on this consumer.
-    @GuardedBy("mLock") @Nullable private Consumer<List<PackageConfig>> mSupportedConsumer;
-    // Called everytime we need to notify the watchdog to sync requests between itself and the
-    // health check service. In practice, should never be null after it has been #setEnabled.
-    // To prevent deadlocks between the controller and watchdog threads, we have
-    // a lock invariant to ALWAYS acquire the PackageWatchdog#mLock before #mLock in this class.
-    // It's easier to just NOT hold #mLock when calling into watchdog code on this runnable.
-    @GuardedBy("mLock") @Nullable private Runnable mNotifySyncRunnable;
-    // Actual binder object to the explicit health check service.
-    @GuardedBy("mLock") @Nullable private IExplicitHealthCheckService mRemoteService;
-    // Connection to the explicit health check service, necessary to unbind.
-    // We should only try to bind if mConnection is null, non-null indicates we
-    // are connected or at least connecting.
-    @GuardedBy("mLock") @Nullable private ServiceConnection mConnection;
-    // Bind state of the explicit health check service.
-    @GuardedBy("mLock") private boolean mEnabled;
-
-    ExplicitHealthCheckController(Context context) {
-        mContext = context;
-    }
-
-    /** Enables or disables explicit health checks. */
-    public void setEnabled(boolean enabled) {
-        synchronized (mLock) {
-            Slog.i(TAG, "Explicit health checks " + (enabled ? "enabled." : "disabled."));
-            mEnabled = enabled;
-        }
-    }
-
-    /**
-     * Sets callbacks to listen to important events from the controller.
-     *
-     * <p> Should be called once at initialization before any other calls to the controller to
-     * ensure a happens-before relationship of the set parameters and visibility on other threads.
-     */
-    public void setCallbacks(Consumer<String> passedConsumer,
-            Consumer<List<PackageConfig>> supportedConsumer, Runnable notifySyncRunnable) {
-        synchronized (mLock) {
-            if (mPassedConsumer != null || mSupportedConsumer != null
-                    || mNotifySyncRunnable != null) {
-                Slog.wtf(TAG, "Resetting health check controller callbacks");
-            }
-
-            mPassedConsumer = Objects.requireNonNull(passedConsumer);
-            mSupportedConsumer = Objects.requireNonNull(supportedConsumer);
-            mNotifySyncRunnable = Objects.requireNonNull(notifySyncRunnable);
-        }
-    }
-
-    /**
-     * Calls the health check service to request or cancel packages based on
-     * {@code newRequestedPackages}.
-     *
-     * <p> Supported packages in {@code newRequestedPackages} that have not been previously
-     * requested will be requested while supported packages not in {@code newRequestedPackages}
-     * but were previously requested will be cancelled.
-     *
-     * <p> This handles binding and unbinding to the health check service as required.
-     *
-     * <p> Note, calling this may modify {@code newRequestedPackages}.
-     *
-     * <p> Note, this method is not thread safe, all calls should be serialized.
-     */
-    public void syncRequests(Set<String> newRequestedPackages) {
-        boolean enabled;
-        synchronized (mLock) {
-            enabled = mEnabled;
-        }
-
-        if (!enabled) {
-            Slog.i(TAG, "Health checks disabled, no supported packages");
-            // Call outside lock
-            mSupportedConsumer.accept(Collections.emptyList());
-            return;
-        }
-
-        getSupportedPackages(supportedPackageConfigs -> {
-            // Notify the watchdog without lock held
-            mSupportedConsumer.accept(supportedPackageConfigs);
-            getRequestedPackages(previousRequestedPackages -> {
-                synchronized (mLock) {
-                    // Hold lock so requests and cancellations are sent atomically.
-                    // It is important we don't mix requests from multiple threads.
-
-                    Set<String> supportedPackages = new ArraySet<>();
-                    for (PackageConfig config : supportedPackageConfigs) {
-                        supportedPackages.add(config.getPackageName());
-                    }
-                    // Note, this may modify newRequestedPackages
-                    newRequestedPackages.retainAll(supportedPackages);
-
-                    // Cancel packages no longer requested
-                    actOnDifference(previousRequestedPackages,
-                            newRequestedPackages, p -> cancel(p));
-                    // Request packages not yet requested
-                    actOnDifference(newRequestedPackages,
-                            previousRequestedPackages, p -> request(p));
-
-                    if (newRequestedPackages.isEmpty()) {
-                        Slog.i(TAG, "No more health check requests, unbinding...");
-                        unbindService();
-                        return;
-                    }
-                }
-            });
-        });
-    }
-
-    private void actOnDifference(Collection<String> collection1, Collection<String> collection2,
-            Consumer<String> action) {
-        Iterator<String> iterator = collection1.iterator();
-        while (iterator.hasNext()) {
-            String packageName = iterator.next();
-            if (!collection2.contains(packageName)) {
-                action.accept(packageName);
-            }
-        }
-    }
-
-    /**
-     * Requests an explicit health check for {@code packageName}.
-     * After this request, the callback registered on {@link #setCallbacks} can receive explicit
-     * health check passed results.
-     */
-    private void request(String packageName) {
-        synchronized (mLock) {
-            if (!prepareServiceLocked("request health check for " + packageName)) {
-                return;
-            }
-
-            Slog.i(TAG, "Requesting health check for package " + packageName);
-            try {
-                mRemoteService.request(packageName);
-            } catch (RemoteException e) {
-                Slog.w(TAG, "Failed to request health check for package " + packageName, e);
-            }
-        }
-    }
-
-    /**
-     * Cancels all explicit health checks for {@code packageName}.
-     * After this request, the callback registered on {@link #setCallbacks} can no longer receive
-     * explicit health check passed results.
-     */
-    private void cancel(String packageName) {
-        synchronized (mLock) {
-            if (!prepareServiceLocked("cancel health check for " + packageName)) {
-                return;
-            }
-
-            Slog.i(TAG, "Cancelling health check for package " + packageName);
-            try {
-                mRemoteService.cancel(packageName);
-            } catch (RemoteException e) {
-                // Do nothing, if the service is down, when it comes up, we will sync requests,
-                // if there's some other error, retrying wouldn't fix anyways.
-                Slog.w(TAG, "Failed to cancel health check for package " + packageName, e);
-            }
-        }
-    }
-
-    /**
-     * Returns the packages that we can request explicit health checks for.
-     * The packages will be returned to the {@code consumer}.
-     */
-    private void getSupportedPackages(Consumer<List<PackageConfig>> consumer) {
-        synchronized (mLock) {
-            if (!prepareServiceLocked("get health check supported packages")) {
-                return;
-            }
-
-            Slog.d(TAG, "Getting health check supported packages");
-            try {
-                mRemoteService.getSupportedPackages(new RemoteCallback(result -> {
-                    List<PackageConfig> packages =
-                            result.getParcelableArrayList(EXTRA_SUPPORTED_PACKAGES, android.service.watchdog.ExplicitHealthCheckService.PackageConfig.class);
-                    Slog.i(TAG, "Explicit health check supported packages " + packages);
-                    consumer.accept(packages);
-                }));
-            } catch (RemoteException e) {
-                // Request failed, treat as if all observed packages are supported, if any packages
-                // expire during this period, we may incorrectly treat it as failing health checks
-                // even if we don't support health checks for the package.
-                Slog.w(TAG, "Failed to get health check supported packages", e);
-            }
-        }
-    }
-
-    /**
-     * Returns the packages for which health checks are currently in progress.
-     * The packages will be returned to the {@code consumer}.
-     */
-    private void getRequestedPackages(Consumer<List<String>> consumer) {
-        synchronized (mLock) {
-            if (!prepareServiceLocked("get health check requested packages")) {
-                return;
-            }
-
-            Slog.d(TAG, "Getting health check requested packages");
-            try {
-                mRemoteService.getRequestedPackages(new RemoteCallback(result -> {
-                    List<String> packages = result.getStringArrayList(EXTRA_REQUESTED_PACKAGES);
-                    Slog.i(TAG, "Explicit health check requested packages " + packages);
-                    consumer.accept(packages);
-                }));
-            } catch (RemoteException e) {
-                // Request failed, treat as if we haven't requested any packages, if any packages
-                // were actually requested, they will not be cancelled now. May be cancelled later
-                Slog.w(TAG, "Failed to get health check requested packages", e);
-            }
-        }
-    }
-
-    /**
-     * Binds to the explicit health check service if the controller is enabled and
-     * not already bound.
-     */
-    private void bindService() {
-        synchronized (mLock) {
-            if (!mEnabled || mConnection != null || mRemoteService != null) {
-                if (!mEnabled) {
-                    Slog.i(TAG, "Not binding to service, service disabled");
-                } else if (mRemoteService != null) {
-                    Slog.i(TAG, "Not binding to service, service already connected");
-                } else {
-                    Slog.i(TAG, "Not binding to service, service already connecting");
-                }
-                return;
-            }
-            ComponentName component = getServiceComponentNameLocked();
-            if (component == null) {
-                Slog.wtf(TAG, "Explicit health check service not found");
-                return;
-            }
-
-            Intent intent = new Intent();
-            intent.setComponent(component);
-            mConnection = new ServiceConnection() {
-                @Override
-                public void onServiceConnected(ComponentName name, IBinder service) {
-                    Slog.i(TAG, "Explicit health check service is connected " + name);
-                    initState(service);
-                }
-
-                @Override
-                @MainThread
-                public void onServiceDisconnected(ComponentName name) {
-                    // Service crashed or process was killed, #onServiceConnected will be called.
-                    // Don't need to re-bind.
-                    Slog.i(TAG, "Explicit health check service is disconnected " + name);
-                    synchronized (mLock) {
-                        mRemoteService = null;
-                    }
-                }
-
-                @Override
-                public void onBindingDied(ComponentName name) {
-                    // Application hosting service probably got updated
-                    // Need to re-bind.
-                    Slog.i(TAG, "Explicit health check service binding is dead. Rebind: " + name);
-                    unbindService();
-                    bindService();
-                }
-
-                @Override
-                public void onNullBinding(ComponentName name) {
-                    // Should never happen. Service returned null from #onBind.
-                    Slog.wtf(TAG, "Explicit health check service binding is null?? " + name);
-                }
-            };
-
-            mContext.bindServiceAsUser(intent, mConnection,
-                    Context.BIND_AUTO_CREATE, UserHandle.SYSTEM);
-            Slog.i(TAG, "Explicit health check service is bound");
-        }
-    }
-
-    /** Unbinds the explicit health check service. */
-    private void unbindService() {
-        synchronized (mLock) {
-            if (mRemoteService != null) {
-                mContext.unbindService(mConnection);
-                mRemoteService = null;
-                mConnection = null;
-            }
-            Slog.i(TAG, "Explicit health check service is unbound");
-        }
-    }
-
-    @GuardedBy("mLock")
-    @Nullable
-    private ServiceInfo getServiceInfoLocked() {
-        final Intent intent = new Intent(ExplicitHealthCheckService.SERVICE_INTERFACE);
-        final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
-                PackageManager.GET_SERVICES | PackageManager.GET_META_DATA
-                        |  PackageManager.MATCH_SYSTEM_ONLY);
-        if (resolveInfo == null || resolveInfo.serviceInfo == null) {
-            Slog.w(TAG, "No valid components found.");
-            return null;
-        }
-        return resolveInfo.serviceInfo;
-    }
-
-    @GuardedBy("mLock")
-    @Nullable
-    private ComponentName getServiceComponentNameLocked() {
-        final ServiceInfo serviceInfo = getServiceInfoLocked();
-        if (serviceInfo == null) {
-            return null;
-        }
-
-        final ComponentName name = new ComponentName(serviceInfo.packageName, serviceInfo.name);
-        if (!Manifest.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE
-                .equals(serviceInfo.permission)) {
-            Slog.w(TAG, name.flattenToShortString() + " does not require permission "
-                    + Manifest.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE);
-            return null;
-        }
-        return name;
-    }
-
-    private void initState(IBinder service) {
-        synchronized (mLock) {
-            if (!mEnabled) {
-                Slog.w(TAG, "Attempting to connect disabled service?? Unbinding...");
-                // Very unlikely, but we disabled the service after binding but before we connected
-                unbindService();
-                return;
-            }
-            mRemoteService = IExplicitHealthCheckService.Stub.asInterface(service);
-            try {
-                mRemoteService.setCallback(new RemoteCallback(result -> {
-                    String packageName = result.getString(EXTRA_HEALTH_CHECK_PASSED_PACKAGE);
-                    if (!TextUtils.isEmpty(packageName)) {
-                        if (mPassedConsumer == null) {
-                            Slog.wtf(TAG, "Health check passed for package " + packageName
-                                    + "but no consumer registered.");
-                        } else {
-                            // Call without lock held
-                            mPassedConsumer.accept(packageName);
-                        }
-                    } else {
-                        Slog.wtf(TAG, "Empty package passed explicit health check?");
-                    }
-                }));
-                Slog.i(TAG, "Service initialized, syncing requests");
-            } catch (RemoteException e) {
-                Slog.wtf(TAG, "Could not setCallback on explicit health check service");
-            }
-        }
-        // Calling outside lock
-        mNotifySyncRunnable.run();
-    }
-
-    /**
-     * Prepares the health check service to receive requests.
-     *
-     * @return {@code true} if it is ready and we can proceed with a request,
-     * {@code false} otherwise. If it is not ready, and the service is enabled,
-     * we will bind and the request should be automatically attempted later.
-     */
-    @GuardedBy("mLock")
-    private boolean prepareServiceLocked(String action) {
-        if (mRemoteService != null && mEnabled) {
-            return true;
-        }
-        Slog.i(TAG, "Service not ready to " + action
-                + (mEnabled ? ". Binding..." : ". Disabled"));
-        if (mEnabled) {
-            bindService();
-        }
-        return false;
-    }
-}
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java
deleted file mode 100644
index e4f07f9..0000000
--- a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java
+++ /dev/null
@@ -1,2253 +0,0 @@
-/*
- * Copyright (C) 2018 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;
-
-import static android.content.Intent.ACTION_REBOOT;
-import static android.content.Intent.ACTION_SHUTDOWN;
-import static android.service.watchdog.ExplicitHealthCheckService.PackageConfig;
-import static android.util.Xml.Encoding.UTF_8;
-
-import static com.android.server.crashrecovery.CrashRecoveryUtils.dumpCrashRecoveryEvents;
-
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import android.annotation.CallbackExecutor;
-import android.annotation.FlaggedApi;
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SuppressLint;
-import android.annotation.SystemApi;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.VersionedPackage;
-import android.crashrecovery.flags.Flags;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Process;
-import android.os.SystemProperties;
-import android.provider.DeviceConfig;
-import android.sysprop.CrashRecoveryProperties;
-import android.text.TextUtils;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.AtomicFile;
-import android.util.EventLog;
-import android.util.IndentingPrintWriter;
-import android.util.LongArrayQueue;
-import android.util.Slog;
-import android.util.Xml;
-import android.util.XmlUtils;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.FastXmlSerializer;
-import com.android.modules.utils.BackgroundThread;
-
-import libcore.io.IoUtils;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.FileReader;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.NoSuchElementException;
-import java.util.Set;
-import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Monitors the health of packages on the system and notifies interested observers when packages
- * fail. On failure, the registered observer with the least user impacting mitigation will
- * be notified.
- * @hide
- */
-@FlaggedApi(Flags.FLAG_ENABLE_CRASHRECOVERY)
-@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
-public class PackageWatchdog {
-    private static final String TAG = "PackageWatchdog";
-
-    static final String PROPERTY_WATCHDOG_TRIGGER_DURATION_MILLIS =
-            "watchdog_trigger_failure_duration_millis";
-    static final String PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT =
-            "watchdog_trigger_failure_count";
-    static final String PROPERTY_WATCHDOG_EXPLICIT_HEALTH_CHECK_ENABLED =
-            "watchdog_explicit_health_check_enabled";
-
-    // TODO: make the following values configurable via DeviceConfig
-    private static final long NATIVE_CRASH_POLLING_INTERVAL_MILLIS =
-            TimeUnit.SECONDS.toMillis(30);
-    private static final long NUMBER_OF_NATIVE_CRASH_POLLS = 10;
-
-
-    /** Reason for package failure could not be determined. */
-    public static final int FAILURE_REASON_UNKNOWN = 0;
-
-    /** The package had a native crash. */
-    public static final int FAILURE_REASON_NATIVE_CRASH = 1;
-
-    /** The package failed an explicit health check. */
-    public static final int FAILURE_REASON_EXPLICIT_HEALTH_CHECK = 2;
-
-    /** The app crashed. */
-    public static final int FAILURE_REASON_APP_CRASH = 3;
-
-    /** The app was not responding. */
-    public static final int FAILURE_REASON_APP_NOT_RESPONDING = 4;
-
-    /** The device was boot looping. */
-    public static final int FAILURE_REASON_BOOT_LOOP = 5;
-
-    /** @hide */
-    @IntDef(prefix = { "FAILURE_REASON_" }, value = {
-            FAILURE_REASON_UNKNOWN,
-            FAILURE_REASON_NATIVE_CRASH,
-            FAILURE_REASON_EXPLICIT_HEALTH_CHECK,
-            FAILURE_REASON_APP_CRASH,
-            FAILURE_REASON_APP_NOT_RESPONDING,
-            FAILURE_REASON_BOOT_LOOP
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface FailureReasons {}
-
-    // Duration to count package failures before it resets to 0
-    @VisibleForTesting
-    static final int DEFAULT_TRIGGER_FAILURE_DURATION_MS =
-            (int) TimeUnit.MINUTES.toMillis(1);
-    // Number of package failures within the duration above before we notify observers
-    @VisibleForTesting
-    static final int DEFAULT_TRIGGER_FAILURE_COUNT = 5;
-    @VisibleForTesting
-    static final long DEFAULT_OBSERVING_DURATION_MS = TimeUnit.DAYS.toMillis(2);
-    // Sliding window for tracking how many mitigation calls were made for a package.
-    @VisibleForTesting
-    static final long DEFAULT_DEESCALATION_WINDOW_MS = TimeUnit.HOURS.toMillis(1);
-    // Whether explicit health checks are enabled or not
-    private static final boolean DEFAULT_EXPLICIT_HEALTH_CHECK_ENABLED = true;
-
-    @VisibleForTesting
-    static final int DEFAULT_BOOT_LOOP_TRIGGER_COUNT = 5;
-
-    static final long DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS = TimeUnit.MINUTES.toMillis(10);
-
-    // Time needed to apply mitigation
-    private static final String MITIGATION_WINDOW_MS =
-            "persist.device_config.configuration.mitigation_window_ms";
-    @VisibleForTesting
-    static final long DEFAULT_MITIGATION_WINDOW_MS = TimeUnit.SECONDS.toMillis(5);
-
-    // Threshold level at which or above user might experience significant disruption.
-    private static final String MAJOR_USER_IMPACT_LEVEL_THRESHOLD =
-            "persist.device_config.configuration.major_user_impact_level_threshold";
-    private static final int DEFAULT_MAJOR_USER_IMPACT_LEVEL_THRESHOLD =
-            PackageHealthObserverImpact.USER_IMPACT_LEVEL_71;
-
-    // Comma separated list of all packages exempt from user impact level threshold. If a package
-    // in the list is crash looping, all the mitigations including factory reset will be performed.
-    private static final String PACKAGES_EXEMPT_FROM_IMPACT_LEVEL_THRESHOLD =
-            "persist.device_config.configuration.packages_exempt_from_impact_level_threshold";
-
-    // Comma separated list of default packages exempt from user impact level threshold.
-    private static final String DEFAULT_PACKAGES_EXEMPT_FROM_IMPACT_LEVEL_THRESHOLD =
-            "com.android.systemui";
-
-    private long mNumberOfNativeCrashPollsRemaining;
-
-    private static final int DB_VERSION = 1;
-    private static final String TAG_PACKAGE_WATCHDOG = "package-watchdog";
-    private static final String TAG_PACKAGE = "package";
-    private static final String TAG_OBSERVER = "observer";
-    private static final String ATTR_VERSION = "version";
-    private static final String ATTR_NAME = "name";
-    private static final String ATTR_DURATION = "duration";
-    private static final String ATTR_EXPLICIT_HEALTH_CHECK_DURATION = "health-check-duration";
-    private static final String ATTR_PASSED_HEALTH_CHECK = "passed-health-check";
-    private static final String ATTR_MITIGATION_CALLS = "mitigation-calls";
-    private static final String ATTR_MITIGATION_COUNT = "mitigation-count";
-
-    // A file containing information about the current mitigation count in the case of a boot loop.
-    // This allows boot loop information to persist in the case of an fs-checkpoint being
-    // aborted.
-    private static final String METADATA_FILE = "/metadata/watchdog/mitigation_count.txt";
-
-    /**
-     * EventLog tags used when logging into the event log. Note the values must be sync with
-     * frameworks/base/services/core/java/com/android/server/EventLogTags.logtags to get correct
-     * name translation.
-     */
-    private static final int LOG_TAG_RESCUE_NOTE = 2900;
-
-    private static final Object sPackageWatchdogLock = new Object();
-    @GuardedBy("sPackageWatchdogLock")
-    private static PackageWatchdog sPackageWatchdog;
-
-    private static final Object sLock = new Object();
-    // System server context
-    private final Context mContext;
-    // Handler to run short running tasks
-    private final Handler mShortTaskHandler;
-    // Handler for processing IO and long running tasks
-    private final Handler mLongTaskHandler;
-    // Contains (observer-name -> observer-handle) that have ever been registered from
-    // previous boots. Observers with all packages expired are periodically pruned.
-    // It is saved to disk on system shutdown and repouplated on startup so it survives reboots.
-    @GuardedBy("sLock")
-    private final ArrayMap<String, ObserverInternal> mAllObservers = new ArrayMap<>();
-    // File containing the XML data of monitored packages /data/system/package-watchdog.xml
-    private final AtomicFile mPolicyFile;
-    private final ExplicitHealthCheckController mHealthCheckController;
-    private final Runnable mSyncRequests = this::syncRequests;
-    private final Runnable mSyncStateWithScheduledReason = this::syncStateWithScheduledReason;
-    private final Runnable mSaveToFile = this::saveToFile;
-    private final SystemClock mSystemClock;
-    private final BootThreshold mBootThreshold;
-    private final DeviceConfig.OnPropertiesChangedListener
-            mOnPropertyChangedListener = this::onPropertyChanged;
-
-    private final Set<String> mPackagesExemptFromImpactLevelThreshold = new ArraySet<>();
-
-    // The set of packages that have been synced with the ExplicitHealthCheckController
-    @GuardedBy("sLock")
-    private Set<String> mRequestedHealthCheckPackages = new ArraySet<>();
-    @GuardedBy("sLock")
-    private boolean mIsPackagesReady;
-    // Flag to control whether explicit health checks are supported or not
-    @GuardedBy("sLock")
-    private boolean mIsHealthCheckEnabled = DEFAULT_EXPLICIT_HEALTH_CHECK_ENABLED;
-    @GuardedBy("sLock")
-    private int mTriggerFailureDurationMs = DEFAULT_TRIGGER_FAILURE_DURATION_MS;
-    @GuardedBy("sLock")
-    private int mTriggerFailureCount = DEFAULT_TRIGGER_FAILURE_COUNT;
-    // SystemClock#uptimeMillis when we last executed #syncState
-    // 0 if no prune is scheduled.
-    @GuardedBy("sLock")
-    private long mUptimeAtLastStateSync;
-    // If true, sync explicit health check packages with the ExplicitHealthCheckController.
-    @GuardedBy("sLock")
-    private boolean mSyncRequired = false;
-
-    @GuardedBy("sLock")
-    private long mLastMitigation = -1000000;
-
-    @FunctionalInterface
-    @VisibleForTesting
-    interface SystemClock {
-        long uptimeMillis();
-    }
-
-    private PackageWatchdog(Context context) {
-        // Needs to be constructed inline
-        this(context, new AtomicFile(
-                        new File(new File(Environment.getDataDirectory(), "system"),
-                                "package-watchdog.xml")),
-                new Handler(Looper.myLooper()), BackgroundThread.getHandler(),
-                new ExplicitHealthCheckController(context),
-                android.os.SystemClock::uptimeMillis);
-    }
-
-    /**
-     * Creates a PackageWatchdog that allows injecting dependencies.
-     */
-    @VisibleForTesting
-    PackageWatchdog(Context context, AtomicFile policyFile, Handler shortTaskHandler,
-            Handler longTaskHandler, ExplicitHealthCheckController controller,
-            SystemClock clock) {
-        mContext = context;
-        mPolicyFile = policyFile;
-        mShortTaskHandler = shortTaskHandler;
-        mLongTaskHandler = longTaskHandler;
-        mHealthCheckController = controller;
-        mSystemClock = clock;
-        mNumberOfNativeCrashPollsRemaining = NUMBER_OF_NATIVE_CRASH_POLLS;
-        mBootThreshold = new BootThreshold(DEFAULT_BOOT_LOOP_TRIGGER_COUNT,
-                DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS);
-
-        loadFromFile();
-        sPackageWatchdog = this;
-    }
-
-    /**
-     * Creates or gets singleton instance of PackageWatchdog.
-     *
-     * @param context The system server context.
-     */
-    public static  @NonNull PackageWatchdog getInstance(@NonNull Context context) {
-        synchronized (sPackageWatchdogLock) {
-            if (sPackageWatchdog == null) {
-                new PackageWatchdog(context);
-            }
-            return sPackageWatchdog;
-        }
-    }
-
-    /**
-     * Called during boot to notify when packages are ready on the device so we can start
-     * binding.
-     * @hide
-     */
-    public void onPackagesReady() {
-        synchronized (sLock) {
-            mIsPackagesReady = true;
-            mHealthCheckController.setCallbacks(packageName -> onHealthCheckPassed(packageName),
-                    packages -> onSupportedPackages(packages),
-                    this::onSyncRequestNotified);
-            setPropertyChangedListenerLocked();
-            updateConfigs();
-        }
-    }
-
-    /**
-     * Registers {@code observer} to listen for package failures. Add a new ObserverInternal for
-     * this observer if it does not already exist.
-     * For executing mitigations observers will receive callback on the given executor.
-     *
-     * <p>Observers are expected to call this on boot. It does not specify any packages but
-     * it will resume observing any packages requested from a previous boot.
-     *
-     * @param observer instance of {@link PackageHealthObserver} for observing package failures
-     *                 and boot loops.
-     * @param executor Executor for the thread on which observers would receive callbacks
-     */
-    public void registerHealthObserver(@NonNull @CallbackExecutor Executor executor,
-            @NonNull PackageHealthObserver observer) {
-        synchronized (sLock) {
-            ObserverInternal internalObserver = mAllObservers.get(observer.getUniqueIdentifier());
-            if (internalObserver != null) {
-                internalObserver.registeredObserver = observer;
-                internalObserver.observerExecutor = executor;
-            } else {
-                internalObserver = new ObserverInternal(observer.getUniqueIdentifier(),
-                        new ArrayList<>());
-                internalObserver.registeredObserver = observer;
-                internalObserver.observerExecutor = executor;
-                mAllObservers.put(observer.getUniqueIdentifier(), internalObserver);
-                syncState("added new observer");
-            }
-        }
-    }
-
-    /**
-     * Starts observing the health of the {@code packages} for {@code observer}.
-     * Note: Observer needs to be registered with {@link #registerHealthObserver} before calling
-     * this API.
-     *
-     * <p>If monitoring a package supporting explicit health check, at the end of the monitoring
-     * duration if {@link #onHealthCheckPassed} was never called,
-     * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} will be called as if the
-     * package failed.
-     *
-     * <p>If {@code observer} is already monitoring a package in {@code packageNames},
-     * the monitoring window of that package will be reset to {@code durationMs} and the health
-     * check state will be reset to a default.
-     *
-     * <p>The {@code observer} must be registered with {@link #registerHealthObserver} before
-     * calling this method.
-     *
-     * @param packageNames The list of packages to check. If this is empty, the call will be a
-     *                     no-op.
-     *
-     * @param timeoutMs The timeout after which Explicit Health Checks would not run. If this is
-     *                  less than 1, a default monitoring duration 2 days will be used.
-     *
-     * @throws IllegalStateException if the observer was not previously registered
-     */
-    public void startExplicitHealthCheck(@NonNull List<String> packageNames, long timeoutMs,
-            @NonNull PackageHealthObserver observer) {
-        synchronized (sLock) {
-            if (!mAllObservers.containsKey(observer.getUniqueIdentifier())) {
-                Slog.wtf(TAG, "No observer found, need to register the observer: "
-                        + observer.getUniqueIdentifier());
-                throw new IllegalStateException("Observer not registered");
-            }
-        }
-        if (packageNames.isEmpty()) {
-            Slog.wtf(TAG, "No packages to observe, " + observer.getUniqueIdentifier());
-            return;
-        }
-        if (timeoutMs < 1) {
-            Slog.wtf(TAG, "Invalid duration " + timeoutMs + "ms for observer "
-                    + observer.getUniqueIdentifier() + ". Not observing packages " + packageNames);
-            timeoutMs = DEFAULT_OBSERVING_DURATION_MS;
-        }
-
-        List<MonitoredPackage> packages = new ArrayList<>();
-        for (int i = 0; i < packageNames.size(); i++) {
-            // Health checks not available yet so health check state will start INACTIVE
-            MonitoredPackage pkg = newMonitoredPackage(packageNames.get(i), timeoutMs, false);
-            if (pkg != null) {
-                packages.add(pkg);
-            } else {
-                Slog.w(TAG, "Failed to create MonitoredPackage for pkg=" + packageNames.get(i));
-            }
-        }
-
-        if (packages.isEmpty()) {
-            return;
-        }
-
-        // Sync before we add the new packages to the observers. This will #pruneObservers,
-        // causing any elapsed time to be deducted from all existing packages before we add new
-        // packages. This maintains the invariant that the elapsed time for ALL (new and existing)
-        // packages is the same.
-        mLongTaskHandler.post(() -> {
-            syncState("observing new packages");
-
-            synchronized (sLock) {
-                ObserverInternal oldObserver = mAllObservers.get(observer.getUniqueIdentifier());
-                if (oldObserver == null) {
-                    Slog.d(TAG, observer.getUniqueIdentifier() + " started monitoring health "
-                            + "of packages " + packageNames);
-                    mAllObservers.put(observer.getUniqueIdentifier(),
-                            new ObserverInternal(observer.getUniqueIdentifier(), packages));
-                } else {
-                    Slog.d(TAG, observer.getUniqueIdentifier() + " added the following "
-                            + "packages to monitor " + packageNames);
-                    oldObserver.updatePackagesLocked(packages);
-                }
-            }
-
-            // Sync after we add the new packages to the observers. We may have received packges
-            // requiring an earlier schedule than we are currently scheduled for.
-            syncState("updated observers");
-        });
-
-    }
-
-    /**
-     * Unregisters {@code observer} from listening to package failure.
-     * Additionally, this stops observing any packages that may have previously been observed
-     * even from a previous boot.
-     */
-    public void unregisterHealthObserver(@NonNull PackageHealthObserver observer) {
-        mLongTaskHandler.post(() -> {
-            synchronized (sLock) {
-                mAllObservers.remove(observer.getUniqueIdentifier());
-            }
-            syncState("unregistering observer: " + observer.getUniqueIdentifier());
-        });
-    }
-
-    /**
-     * Called when a process fails due to a crash, ANR or explicit health check.
-     *
-     * <p>For each package contained in the process, one registered observer with the least user
-     * impact will be notified for mitigation.
-     *
-     * <p>This method could be called frequently if there is a severe problem on the device.
-     */
-    public void notifyPackageFailure(@NonNull List<VersionedPackage> packages,
-            @FailureReasons int failureReason) {
-        if (packages == null) {
-            Slog.w(TAG, "Could not resolve a list of failing packages");
-            return;
-        }
-        synchronized (sLock) {
-            final long now = mSystemClock.uptimeMillis();
-            if (Flags.recoverabilityDetection()) {
-                if (now >= mLastMitigation
-                        && (now - mLastMitigation) < getMitigationWindowMs()) {
-                    Slog.i(TAG, "Skipping notifyPackageFailure mitigation");
-                    return;
-                }
-            }
-        }
-        mLongTaskHandler.post(() -> {
-            synchronized (sLock) {
-                if (mAllObservers.isEmpty()) {
-                    return;
-                }
-                boolean requiresImmediateAction = (failureReason == FAILURE_REASON_NATIVE_CRASH
-                        || failureReason == FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
-                if (requiresImmediateAction) {
-                    handleFailureImmediately(packages, failureReason);
-                } else {
-                    for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
-                        VersionedPackage versionedPackage = packages.get(pIndex);
-                        // Observer that will receive failure for versionedPackage
-                        ObserverInternal currentObserverToNotify = null;
-                        int currentObserverImpact = Integer.MAX_VALUE;
-                        MonitoredPackage currentMonitoredPackage = null;
-
-                        // Find observer with least user impact
-                        for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
-                            ObserverInternal observer = mAllObservers.valueAt(oIndex);
-                            PackageHealthObserver registeredObserver = observer.registeredObserver;
-                            if (registeredObserver != null
-                                    && observer.notifyPackageFailureLocked(
-                                    versionedPackage.getPackageName())) {
-                                MonitoredPackage p = observer.getMonitoredPackage(
-                                        versionedPackage.getPackageName());
-                                int mitigationCount = 1;
-                                if (p != null) {
-                                    mitigationCount = p.getMitigationCountLocked() + 1;
-                                }
-                                int impact = registeredObserver.onHealthCheckFailed(
-                                        versionedPackage, failureReason, mitigationCount);
-                                if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0
-                                        && impact < currentObserverImpact) {
-                                    currentObserverToNotify = observer;
-                                    currentObserverImpact = impact;
-                                    currentMonitoredPackage = p;
-                                }
-                            }
-                        }
-
-                        // Execute action with least user impact
-                        if (currentObserverToNotify != null) {
-                            int mitigationCount;
-                            if (currentMonitoredPackage != null) {
-                                currentMonitoredPackage.noteMitigationCallLocked();
-                                mitigationCount =
-                                        currentMonitoredPackage.getMitigationCountLocked();
-                            } else {
-                                mitigationCount = 1;
-                            }
-                            if (Flags.recoverabilityDetection()) {
-                                maybeExecute(currentObserverToNotify, versionedPackage,
-                                        failureReason, currentObserverImpact, mitigationCount);
-                            } else {
-                                PackageHealthObserver registeredObserver =
-                                        currentObserverToNotify.registeredObserver;
-                                currentObserverToNotify.observerExecutor.execute(() ->
-                                        registeredObserver.onExecuteHealthCheckMitigation(
-                                                versionedPackage, failureReason, mitigationCount));
-                            }
-                        }
-                    }
-                }
-            }
-        });
-    }
-
-    /**
-     * For native crashes or explicit health check failures, call directly into each observer to
-     * mitigate the error without going through failure threshold logic.
-     */
-    @GuardedBy("sLock")
-    private void handleFailureImmediately(List<VersionedPackage> packages,
-            @FailureReasons int failureReason) {
-        VersionedPackage failingPackage = packages.size() > 0 ? packages.get(0) : null;
-        ObserverInternal currentObserverToNotify = null;
-        int currentObserverImpact = Integer.MAX_VALUE;
-        for (ObserverInternal observer: mAllObservers.values()) {
-            PackageHealthObserver registeredObserver = observer.registeredObserver;
-            if (registeredObserver != null) {
-                int impact = registeredObserver.onHealthCheckFailed(
-                        failingPackage, failureReason, 1);
-                if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0
-                        && impact < currentObserverImpact) {
-                    currentObserverToNotify = observer;
-                    currentObserverImpact = impact;
-                }
-            }
-        }
-        if (currentObserverToNotify != null) {
-            if (Flags.recoverabilityDetection()) {
-                maybeExecute(currentObserverToNotify, failingPackage, failureReason,
-                        currentObserverImpact, /*mitigationCount=*/ 1);
-            } else {
-                PackageHealthObserver registeredObserver =
-                        currentObserverToNotify.registeredObserver;
-                currentObserverToNotify.observerExecutor.execute(() ->
-                        registeredObserver.onExecuteHealthCheckMitigation(failingPackage,
-                                failureReason, 1));
-
-            }
-        }
-    }
-
-    private void maybeExecute(ObserverInternal currentObserverToNotify,
-                              VersionedPackage versionedPackage,
-                              @FailureReasons int failureReason,
-                              int currentObserverImpact,
-                              int mitigationCount) {
-        if (allowMitigations(currentObserverImpact, versionedPackage)) {
-            PackageHealthObserver registeredObserver;
-            synchronized (sLock) {
-                mLastMitigation = mSystemClock.uptimeMillis();
-                registeredObserver = currentObserverToNotify.registeredObserver;
-            }
-            currentObserverToNotify.observerExecutor.execute(() ->
-                    registeredObserver.onExecuteHealthCheckMitigation(versionedPackage,
-                            failureReason, mitigationCount));
-        }
-    }
-
-    private boolean allowMitigations(int currentObserverImpact,
-            VersionedPackage versionedPackage) {
-        return currentObserverImpact < getUserImpactLevelLimit()
-                || getPackagesExemptFromImpactLevelThreshold().contains(
-                versionedPackage.getPackageName());
-    }
-
-    private long getMitigationWindowMs() {
-        return SystemProperties.getLong(MITIGATION_WINDOW_MS, DEFAULT_MITIGATION_WINDOW_MS);
-    }
-
-
-    /**
-     * Called when the system server boots. If the system server is detected to be in a boot loop,
-     * query each observer and perform the mitigation action with the lowest user impact.
-     *
-     * Note: PackageWatchdog considers system_server restart loop as bootloop. Full reboots
-     * are not counted in bootloop.
-     * @hide
-     */
-    @SuppressWarnings("GuardedBy")
-    public void noteBoot() {
-        synchronized (sLock) {
-            // if boot count has reached threshold, start mitigation.
-            // We wait until threshold number of restarts only for the first time. Perform
-            // mitigations for every restart after that.
-            boolean mitigate = mBootThreshold.incrementAndTest();
-            if (mitigate) {
-                if (!Flags.recoverabilityDetection()) {
-                    mBootThreshold.reset();
-                }
-                int mitigationCount = mBootThreshold.getMitigationCount() + 1;
-                ObserverInternal currentObserverToNotify = null;
-                int currentObserverImpact = Integer.MAX_VALUE;
-                for (int i = 0; i < mAllObservers.size(); i++) {
-                    final ObserverInternal observer = mAllObservers.valueAt(i);
-                    PackageHealthObserver registeredObserver = observer.registeredObserver;
-                    if (registeredObserver != null) {
-                        int impact = Flags.recoverabilityDetection()
-                                ? registeredObserver.onBootLoop(
-                                        observer.getBootMitigationCount() + 1)
-                                : registeredObserver.onBootLoop(mitigationCount);
-                        if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0
-                                && impact < currentObserverImpact) {
-                            currentObserverToNotify = observer;
-                            currentObserverImpact = impact;
-                        }
-                    }
-                }
-
-                if (currentObserverToNotify != null) {
-                    PackageHealthObserver registeredObserver =
-                            currentObserverToNotify.registeredObserver;
-                    if (Flags.recoverabilityDetection()) {
-                        int currentObserverMitigationCount =
-                                currentObserverToNotify.getBootMitigationCount() + 1;
-                        currentObserverToNotify.setBootMitigationCount(
-                                currentObserverMitigationCount);
-                        saveAllObserversBootMitigationCountToMetadata(METADATA_FILE);
-                        currentObserverToNotify.observerExecutor
-                                .execute(() -> registeredObserver.onExecuteBootLoopMitigation(
-                                        currentObserverMitigationCount));
-                    } else {
-                        mBootThreshold.setMitigationCount(mitigationCount);
-                        mBootThreshold.saveMitigationCountToMetadata();
-                        currentObserverToNotify.observerExecutor
-                                .execute(() -> registeredObserver.onExecuteBootLoopMitigation(
-                                        mitigationCount));
-
-                    }
-                }
-            }
-        }
-    }
-
-    // TODO(b/120598832): Optimize write? Maybe only write a separate smaller file? Also
-    // avoid holding lock?
-    // This currently adds about 7ms extra to shutdown thread
-    /** @hide Writes the package information to file during shutdown. */
-    public void writeNow() {
-        synchronized (sLock) {
-            // Must only run synchronous tasks as this runs on the ShutdownThread and no other
-            // thread is guaranteed to run during shutdown.
-            if (!mAllObservers.isEmpty()) {
-                mLongTaskHandler.removeCallbacks(mSaveToFile);
-                pruneObserversLocked();
-                saveToFile();
-                Slog.i(TAG, "Last write to update package durations");
-            }
-        }
-    }
-
-    /**
-     * Enables or disables explicit health checks.
-     * <p> If explicit health checks are enabled, the health check service is started.
-     * <p> If explicit health checks are disabled, pending explicit health check requests are
-     * passed and the health check service is stopped.
-     */
-    private void setExplicitHealthCheckEnabled(boolean enabled) {
-        synchronized (sLock) {
-            mIsHealthCheckEnabled = enabled;
-            mHealthCheckController.setEnabled(enabled);
-            mSyncRequired = true;
-            // Prune to update internal state whenever health check is enabled/disabled
-            syncState("health check state " + (enabled ? "enabled" : "disabled"));
-        }
-    }
-
-    /**
-     * This method should be only called on mShortTaskHandler, since it modifies
-     * {@link #mNumberOfNativeCrashPollsRemaining}.
-     */
-    private void checkAndMitigateNativeCrashes() {
-        mNumberOfNativeCrashPollsRemaining--;
-        // Check if native watchdog reported a crash
-        if ("1".equals(SystemProperties.get("sys.init.updatable_crashing"))) {
-            // We rollback all available low impact rollbacks when crash is unattributable
-            notifyPackageFailure(Collections.EMPTY_LIST, FAILURE_REASON_NATIVE_CRASH);
-            // we stop polling after an attempt to execute rollback, regardless of whether the
-            // attempt succeeds or not
-        } else {
-            if (mNumberOfNativeCrashPollsRemaining > 0) {
-                mShortTaskHandler.postDelayed(() -> checkAndMitigateNativeCrashes(),
-                        NATIVE_CRASH_POLLING_INTERVAL_MILLIS);
-            }
-        }
-    }
-
-    /**
-     * Since this method can eventually trigger a rollback, it should be called
-     * only once boot has completed {@code onBootCompleted} and not earlier, because the install
-     * session must be entirely completed before we try to rollback.
-     * @hide
-     */
-    public void scheduleCheckAndMitigateNativeCrashes() {
-        Slog.i(TAG, "Scheduling " + mNumberOfNativeCrashPollsRemaining + " polls to check "
-                + "and mitigate native crashes");
-        mShortTaskHandler.post(()->checkAndMitigateNativeCrashes());
-    }
-
-    private int getUserImpactLevelLimit() {
-        return SystemProperties.getInt(MAJOR_USER_IMPACT_LEVEL_THRESHOLD,
-                DEFAULT_MAJOR_USER_IMPACT_LEVEL_THRESHOLD);
-    }
-
-    private Set<String> getPackagesExemptFromImpactLevelThreshold() {
-        if (mPackagesExemptFromImpactLevelThreshold.isEmpty()) {
-            String packageNames = SystemProperties.get(PACKAGES_EXEMPT_FROM_IMPACT_LEVEL_THRESHOLD,
-                    DEFAULT_PACKAGES_EXEMPT_FROM_IMPACT_LEVEL_THRESHOLD);
-            return Set.of(packageNames.split("\\s*,\\s*"));
-        }
-        return mPackagesExemptFromImpactLevelThreshold;
-    }
-
-    /**
-     * Indicates that a mitigation was successfully triggered or executed during
-     * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} or
-     * {@link PackageHealthObserver#onExecuteBootLoopMitigation}.
-     */
-    public static final int MITIGATION_RESULT_SUCCESS =
-            ObserverMitigationResult.MITIGATION_RESULT_SUCCESS;
-
-    /**
-     * Indicates that a mitigation executed during
-     * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} or
-     * {@link PackageHealthObserver#onExecuteBootLoopMitigation} was skipped.
-     */
-    public static final int MITIGATION_RESULT_SKIPPED =
-            ObserverMitigationResult.MITIGATION_RESULT_SKIPPED;
-
-
-    /**
-     * Possible return values of the for mitigations executed during
-     * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} and
-     * {@link PackageHealthObserver#onExecuteBootLoopMitigation}.
-     * @hide
-     */
-    @Retention(SOURCE)
-    @IntDef(prefix = "MITIGATION_RESULT_", value = {
-            ObserverMitigationResult.MITIGATION_RESULT_SUCCESS,
-            ObserverMitigationResult.MITIGATION_RESULT_SKIPPED,
-            })
-    public @interface ObserverMitigationResult {
-        int MITIGATION_RESULT_SUCCESS = 1;
-        int MITIGATION_RESULT_SKIPPED = 2;
-    }
-
-    /**
-     * The minimum value that can be returned by any observer.
-     * It represents that no mitigations were available.
-     */
-    public static final int USER_IMPACT_THRESHOLD_NONE =
-            PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
-
-    /**
-     * The mitigation impact beyond which the user will start noticing the mitigations.
-     */
-    public static final int USER_IMPACT_THRESHOLD_MEDIUM =
-            PackageHealthObserverImpact.USER_IMPACT_LEVEL_20;
-
-    /**
-     * The mitigation impact beyond which the user impact is severely high.
-     */
-    public static final int USER_IMPACT_THRESHOLD_HIGH =
-            PackageHealthObserverImpact.USER_IMPACT_LEVEL_71;
-
-    /**
-     * Possible severity values of the user impact of a
-     * {@link PackageHealthObserver#onExecuteHealthCheckMitigation}.
-     * @hide
-     */
-    @Retention(SOURCE)
-    @IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
-                     PackageHealthObserverImpact.USER_IMPACT_LEVEL_10,
-                     PackageHealthObserverImpact.USER_IMPACT_LEVEL_20,
-                     PackageHealthObserverImpact.USER_IMPACT_LEVEL_30,
-                     PackageHealthObserverImpact.USER_IMPACT_LEVEL_40,
-                     PackageHealthObserverImpact.USER_IMPACT_LEVEL_50,
-                     PackageHealthObserverImpact.USER_IMPACT_LEVEL_70,
-                     PackageHealthObserverImpact.USER_IMPACT_LEVEL_71,
-                     PackageHealthObserverImpact.USER_IMPACT_LEVEL_75,
-                     PackageHealthObserverImpact.USER_IMPACT_LEVEL_80,
-                     PackageHealthObserverImpact.USER_IMPACT_LEVEL_90,
-                     PackageHealthObserverImpact.USER_IMPACT_LEVEL_100})
-    public @interface PackageHealthObserverImpact {
-        /** No action to take. */
-        int USER_IMPACT_LEVEL_0 = 0;
-        /* Action has low user impact, user of a device will barely notice. */
-        int USER_IMPACT_LEVEL_10 = 10;
-        /* Actions having medium user impact, user of a device will likely notice. */
-        int USER_IMPACT_LEVEL_20 = 20;
-        int USER_IMPACT_LEVEL_30 = 30;
-        int USER_IMPACT_LEVEL_40 = 40;
-        int USER_IMPACT_LEVEL_50 = 50;
-        int USER_IMPACT_LEVEL_70 = 70;
-        /* Action has high user impact, a last resort, user of a device will be very frustrated. */
-        int USER_IMPACT_LEVEL_71 = 71;
-        int USER_IMPACT_LEVEL_75 = 75;
-        int USER_IMPACT_LEVEL_80 = 80;
-        int USER_IMPACT_LEVEL_90 = 90;
-        int USER_IMPACT_LEVEL_100 = 100;
-    }
-
-    /** Register instances of this interface to receive notifications on package failure. */
-    @SuppressLint({"CallbackName"})
-    public interface PackageHealthObserver {
-        /**
-         * Called when health check fails for the {@code versionedPackage}.
-         * Note: if the returned user impact is higher than {@link #USER_IMPACT_THRESHOLD_HIGH},
-         * then {@link #onExecuteHealthCheckMitigation} would be called only in severe device
-         * conditions like boot-loop or network failure.
-         *
-         * @param versionedPackage the package that is failing. This may be null if a native
-         *                          service is crashing.
-         * @param failureReason   the type of failure that is occurring.
-         * @param mitigationCount the number of times mitigation has been called for this package
-         *                        (including this time).
-         *
-         * @return any value greater than {@link #USER_IMPACT_THRESHOLD_NONE} to express
-         * the impact of mitigation on the user in {@link #onExecuteHealthCheckMitigation}.
-         * Returning {@link #USER_IMPACT_THRESHOLD_NONE} would indicate no mitigations available.
-         */
-        @PackageHealthObserverImpact int onHealthCheckFailed(
-                @Nullable VersionedPackage versionedPackage,
-                @FailureReasons int failureReason,
-                int mitigationCount);
-
-        /**
-         * This would be called after {@link #onHealthCheckFailed}.
-         * This is called only if current observer returned least impact mitigation for failed
-         * health check.
-         *
-         * @param versionedPackage the package that is failing. This may be null if a native
-         *                         service is crashing.
-         * @param failureReason    the type of failure that is occurring.
-         * @param mitigationCount the number of times mitigation has been called for this package
-         *                         (including this time).
-         * @return {@link #MITIGATION_RESULT_SUCCESS} if the mitigation was successful,
-         *         or {@link #MITIGATION_RESULT_SKIPPED} if the mitigation was skipped.
-         */
-        @ObserverMitigationResult int onExecuteHealthCheckMitigation(
-                @Nullable VersionedPackage versionedPackage,
-                @FailureReasons int failureReason, int mitigationCount);
-
-
-        /**
-         * Called when the system server has booted several times within a window of time, defined
-         * by {@link #mBootThreshold}
-         *
-         * @param mitigationCount the number of times mitigation has been attempted for this
-         *                        boot loop (including this time).
-         *
-         * @return any value greater than {@link #USER_IMPACT_THRESHOLD_NONE} to express
-         * the impact of mitigation on the user in {@link #onExecuteBootLoopMitigation}.
-         * Returning {@link #USER_IMPACT_THRESHOLD_NONE} would indicate no mitigations available.
-         */
-        default @PackageHealthObserverImpact int onBootLoop(int mitigationCount) {
-            return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
-        }
-
-        /**
-         * This would be called after {@link #onBootLoop}.
-         * This is called only if current observer returned least impact mitigation for fixing
-         * boot loop.
-         *
-         * @param mitigationCount the number of times mitigation has been attempted for this
-         *                        boot loop (including this time).
-         *
-         * @return {@link #MITIGATION_RESULT_SUCCESS} if the mitigation was successful,
-         *         or {@link #MITIGATION_RESULT_SKIPPED} if the mitigation was skipped.
-         */
-        default @ObserverMitigationResult int onExecuteBootLoopMitigation(int mitigationCount) {
-            return ObserverMitigationResult.MITIGATION_RESULT_SKIPPED;
-        }
-
-        // TODO(b/120598832): Ensure uniqueness?
-        /**
-         * Identifier for the observer, should not change across device updates otherwise the
-         * watchdog may drop observing packages with the old name.
-         */
-        @NonNull String getUniqueIdentifier();
-
-        /**
-         * An observer will not be pruned if this is set, even if the observer is not explicitly
-         * monitoring any packages.
-         */
-        default boolean isPersistent() {
-            return false;
-        }
-
-        /**
-         * Returns {@code true} if this observer wishes to observe the given package, {@code false}
-         * otherwise.
-         * Any failing package can be passed on to the observer. Currently the packages that have
-         * ANRs and perform {@link android.service.watchdog.ExplicitHealthCheckService} are being
-         * passed to observers in these API.
-         *
-         * <p> A persistent observer may choose to start observing certain failing packages, even if
-         * it has not explicitly asked to watch the package with {@link #startExplicitHealthCheck}.
-         */
-        default boolean mayObservePackage(@NonNull String packageName) {
-            return false;
-        }
-    }
-
-    @VisibleForTesting
-    long getTriggerFailureCount() {
-        synchronized (sLock) {
-            return mTriggerFailureCount;
-        }
-    }
-
-    @VisibleForTesting
-    long getTriggerFailureDurationMs() {
-        synchronized (sLock) {
-            return mTriggerFailureDurationMs;
-        }
-    }
-
-    /**
-     * Serializes and syncs health check requests with the {@link ExplicitHealthCheckController}.
-     */
-    private void syncRequestsAsync() {
-        mShortTaskHandler.removeCallbacks(mSyncRequests);
-        mShortTaskHandler.post(mSyncRequests);
-    }
-
-    /**
-     * Syncs health check requests with the {@link ExplicitHealthCheckController}.
-     * Calls to this must be serialized.
-     *
-     * @see #syncRequestsAsync
-     */
-    private void syncRequests() {
-        boolean syncRequired = false;
-        synchronized (sLock) {
-            if (mIsPackagesReady) {
-                Set<String> packages = getPackagesPendingHealthChecksLocked();
-                if (mSyncRequired || !packages.equals(mRequestedHealthCheckPackages)
-                        || packages.isEmpty()) {
-                    syncRequired = true;
-                    mRequestedHealthCheckPackages = packages;
-                }
-            } // else, we will sync requests when packages become ready
-        }
-
-        // Call outside lock to avoid holding lock when calling into the controller.
-        if (syncRequired) {
-            Slog.i(TAG, "Syncing health check requests for packages: "
-                    + mRequestedHealthCheckPackages);
-            mHealthCheckController.syncRequests(mRequestedHealthCheckPackages);
-            mSyncRequired = false;
-        }
-    }
-
-    /**
-     * Updates the observers monitoring {@code packageName} that explicit health check has passed.
-     *
-     * <p> This update is strictly for registered observers at the time of the call
-     * Observers that register after this signal will have no knowledge of prior signals and will
-     * effectively behave as if the explicit health check hasn't passed for {@code packageName}.
-     *
-     * <p> {@code packageName} can still be considered failed if reported by
-     * {@link #notifyPackageFailureLocked} before the package expires.
-     *
-     * <p> Triggered by components outside the system server when they are fully functional after an
-     * update.
-     */
-    private void onHealthCheckPassed(String packageName) {
-        Slog.i(TAG, "Health check passed for package: " + packageName);
-        boolean isStateChanged = false;
-
-        synchronized (sLock) {
-            for (int observerIdx = 0; observerIdx < mAllObservers.size(); observerIdx++) {
-                ObserverInternal observer = mAllObservers.valueAt(observerIdx);
-                MonitoredPackage monitoredPackage = observer.getMonitoredPackage(packageName);
-
-                if (monitoredPackage != null) {
-                    int oldState = monitoredPackage.getHealthCheckStateLocked();
-                    int newState = monitoredPackage.tryPassHealthCheckLocked();
-                    isStateChanged |= oldState != newState;
-                }
-            }
-        }
-
-        if (isStateChanged) {
-            syncState("health check passed for " + packageName);
-        }
-    }
-
-    private void onSupportedPackages(List<PackageConfig> supportedPackages) {
-        boolean isStateChanged = false;
-
-        Map<String, Long> supportedPackageTimeouts = new ArrayMap<>();
-        Iterator<PackageConfig> it = supportedPackages.iterator();
-        while (it.hasNext()) {
-            PackageConfig info = it.next();
-            supportedPackageTimeouts.put(info.getPackageName(), info.getHealthCheckTimeoutMillis());
-        }
-
-        synchronized (sLock) {
-            Slog.d(TAG, "Received supported packages " + supportedPackages);
-            Iterator<ObserverInternal> oit = mAllObservers.values().iterator();
-            while (oit.hasNext()) {
-                Iterator<MonitoredPackage> pit = oit.next().getMonitoredPackages()
-                        .values().iterator();
-                while (pit.hasNext()) {
-                    MonitoredPackage monitoredPackage = pit.next();
-                    String packageName = monitoredPackage.getName();
-                    int oldState = monitoredPackage.getHealthCheckStateLocked();
-                    int newState;
-
-                    if (supportedPackageTimeouts.containsKey(packageName)) {
-                        // Supported packages become ACTIVE if currently INACTIVE
-                        newState = monitoredPackage.setHealthCheckActiveLocked(
-                                supportedPackageTimeouts.get(packageName));
-                    } else {
-                        // Unsupported packages are marked as PASSED unless already FAILED
-                        newState = monitoredPackage.tryPassHealthCheckLocked();
-                    }
-                    isStateChanged |= oldState != newState;
-                }
-            }
-        }
-
-        if (isStateChanged) {
-            syncState("updated health check supported packages " + supportedPackages);
-        }
-    }
-
-    private void onSyncRequestNotified() {
-        synchronized (sLock) {
-            mSyncRequired = true;
-            syncRequestsAsync();
-        }
-    }
-
-    @GuardedBy("sLock")
-    private Set<String> getPackagesPendingHealthChecksLocked() {
-        Set<String> packages = new ArraySet<>();
-        Iterator<ObserverInternal> oit = mAllObservers.values().iterator();
-        while (oit.hasNext()) {
-            ObserverInternal observer = oit.next();
-            Iterator<MonitoredPackage> pit =
-                    observer.getMonitoredPackages().values().iterator();
-            while (pit.hasNext()) {
-                MonitoredPackage monitoredPackage = pit.next();
-                String packageName = monitoredPackage.getName();
-                if (monitoredPackage.isPendingHealthChecksLocked()) {
-                    packages.add(packageName);
-                }
-            }
-        }
-        return packages;
-    }
-
-    /**
-     * Syncs the state of the observers.
-     *
-     * <p> Prunes all observers, saves new state to disk, syncs health check requests with the
-     * health check service and schedules the next state sync.
-     */
-    private void syncState(String reason) {
-        synchronized (sLock) {
-            Slog.i(TAG, "Syncing state, reason: " + reason);
-            pruneObserversLocked();
-
-            saveToFileAsync();
-            syncRequestsAsync();
-
-            // Done syncing state, schedule the next state sync
-            scheduleNextSyncStateLocked();
-        }
-    }
-
-    private void syncStateWithScheduledReason() {
-        syncState("scheduled");
-    }
-
-    @GuardedBy("sLock")
-    private void scheduleNextSyncStateLocked() {
-        long durationMs = getNextStateSyncMillisLocked();
-        mShortTaskHandler.removeCallbacks(mSyncStateWithScheduledReason);
-        if (durationMs == Long.MAX_VALUE) {
-            Slog.i(TAG, "Cancelling state sync, nothing to sync");
-            mUptimeAtLastStateSync = 0;
-        } else {
-            mUptimeAtLastStateSync = mSystemClock.uptimeMillis();
-            mShortTaskHandler.postDelayed(mSyncStateWithScheduledReason, durationMs);
-        }
-    }
-
-    /**
-     * Returns the next duration in millis to sync the watchdog state.
-     *
-     * @returns Long#MAX_VALUE if there are no observed packages.
-     */
-    @GuardedBy("sLock")
-    private long getNextStateSyncMillisLocked() {
-        long shortestDurationMs = Long.MAX_VALUE;
-        for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
-            ArrayMap<String, MonitoredPackage> packages = mAllObservers.valueAt(oIndex)
-                    .getMonitoredPackages();
-            for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
-                MonitoredPackage mp = packages.valueAt(pIndex);
-                long duration = mp.getShortestScheduleDurationMsLocked();
-                if (duration < shortestDurationMs) {
-                    shortestDurationMs = duration;
-                }
-            }
-        }
-        return shortestDurationMs;
-    }
-
-    /**
-     * Removes {@code elapsedMs} milliseconds from all durations on monitored packages
-     * and updates other internal state.
-     */
-    @GuardedBy("sLock")
-    private void pruneObserversLocked() {
-        long elapsedMs = mUptimeAtLastStateSync == 0
-                ? 0 : mSystemClock.uptimeMillis() - mUptimeAtLastStateSync;
-        if (elapsedMs <= 0) {
-            Slog.i(TAG, "Not pruning observers, elapsed time: " + elapsedMs + "ms");
-            return;
-        }
-
-        Iterator<ObserverInternal> it = mAllObservers.values().iterator();
-        while (it.hasNext()) {
-            ObserverInternal observer = it.next();
-            Set<MonitoredPackage> failedPackages =
-                    observer.prunePackagesLocked(elapsedMs);
-            if (!failedPackages.isEmpty()) {
-                onHealthCheckFailed(observer, failedPackages);
-            }
-            if (observer.getMonitoredPackages().isEmpty() && (observer.registeredObserver == null
-                    || !observer.registeredObserver.isPersistent())) {
-                Slog.i(TAG, "Discarding observer " + observer.name + ". All packages expired");
-                it.remove();
-            }
-        }
-    }
-
-    private void onHealthCheckFailed(ObserverInternal observer,
-            Set<MonitoredPackage> failedPackages) {
-        mLongTaskHandler.post(() -> {
-            synchronized (sLock) {
-                PackageHealthObserver registeredObserver = observer.registeredObserver;
-                if (registeredObserver != null) {
-                    Iterator<MonitoredPackage> it = failedPackages.iterator();
-                    while (it.hasNext()) {
-                        VersionedPackage versionedPkg = getVersionedPackage(it.next().getName());
-                        if (versionedPkg != null) {
-                            Slog.i(TAG,
-                                    "Explicit health check failed for package " + versionedPkg);
-                            observer.observerExecutor.execute(() ->
-                                    registeredObserver.onExecuteHealthCheckMitigation(versionedPkg,
-                                            PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK,
-                                            1));
-                        }
-                    }
-                }
-            }
-        });
-    }
-
-    /**
-     * Gets PackageInfo for the given package. Matches any user and apex.
-     *
-     * @throws PackageManager.NameNotFoundException if no such package is installed.
-     */
-    private PackageInfo getPackageInfo(String packageName)
-            throws PackageManager.NameNotFoundException {
-        PackageManager pm = mContext.getPackageManager();
-        try {
-            // The MATCH_ANY_USER flag doesn't mix well with the MATCH_APEX
-            // flag, so make two separate attempts to get the package info.
-            // We don't need both flags at the same time because we assume
-            // apex files are always installed for all users.
-            return pm.getPackageInfo(packageName, PackageManager.MATCH_ANY_USER);
-        } catch (PackageManager.NameNotFoundException e) {
-            return pm.getPackageInfo(packageName, PackageManager.MATCH_APEX);
-        }
-    }
-
-    @Nullable
-    private VersionedPackage getVersionedPackage(String packageName) {
-        final PackageManager pm = mContext.getPackageManager();
-        if (pm == null || TextUtils.isEmpty(packageName)) {
-            return null;
-        }
-        try {
-            final long versionCode = getPackageInfo(packageName).getLongVersionCode();
-            return new VersionedPackage(packageName, versionCode);
-        } catch (PackageManager.NameNotFoundException e) {
-            return null;
-        }
-    }
-
-    /**
-     * Loads mAllObservers from file.
-     *
-     * <p>Note that this is <b>not</b> thread safe and should only called be called
-     * from the constructor.
-     */
-    private void loadFromFile() {
-        InputStream infile = null;
-        mAllObservers.clear();
-        try {
-            infile = mPolicyFile.openRead();
-            final XmlPullParser parser = Xml.newPullParser();
-            parser.setInput(infile, UTF_8.name());
-            XmlUtils.beginDocument(parser, TAG_PACKAGE_WATCHDOG);
-            int outerDepth = parser.getDepth();
-            while (XmlUtils.nextElementWithin(parser, outerDepth)) {
-                ObserverInternal observer = ObserverInternal.read(parser, this);
-                if (observer != null) {
-                    mAllObservers.put(observer.name, observer);
-                }
-            }
-        } catch (FileNotFoundException e) {
-            // Nothing to monitor
-        } catch (Exception e) {
-            Slog.wtf(TAG, "Unable to read monitored packages, deleting file", e);
-            mPolicyFile.delete();
-        } finally {
-            IoUtils.closeQuietly(infile);
-        }
-    }
-
-    private void onPropertyChanged(DeviceConfig.Properties properties) {
-        try {
-            updateConfigs();
-        } catch (Exception ignore) {
-            Slog.w(TAG, "Failed to reload device config changes");
-        }
-    }
-
-    /** Adds a {@link DeviceConfig#OnPropertiesChangedListener}. */
-    private void setPropertyChangedListenerLocked() {
-        DeviceConfig.addOnPropertiesChangedListener(
-                DeviceConfig.NAMESPACE_ROLLBACK,
-                mContext.getMainExecutor(),
-                mOnPropertyChangedListener);
-    }
-
-    @VisibleForTesting
-    void removePropertyChangedListener() {
-        DeviceConfig.removeOnPropertiesChangedListener(mOnPropertyChangedListener);
-    }
-
-    /**
-     * Health check is enabled or disabled after reading the flags
-     * from DeviceConfig.
-     */
-    @VisibleForTesting
-    void updateConfigs() {
-        synchronized (sLock) {
-            mTriggerFailureCount = DeviceConfig.getInt(
-                    DeviceConfig.NAMESPACE_ROLLBACK,
-                    PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
-                    DEFAULT_TRIGGER_FAILURE_COUNT);
-            if (mTriggerFailureCount <= 0) {
-                mTriggerFailureCount = DEFAULT_TRIGGER_FAILURE_COUNT;
-            }
-
-            mTriggerFailureDurationMs = DeviceConfig.getInt(
-                    DeviceConfig.NAMESPACE_ROLLBACK,
-                    PROPERTY_WATCHDOG_TRIGGER_DURATION_MILLIS,
-                    DEFAULT_TRIGGER_FAILURE_DURATION_MS);
-            if (mTriggerFailureDurationMs <= 0) {
-                mTriggerFailureDurationMs = DEFAULT_TRIGGER_FAILURE_DURATION_MS;
-            }
-
-            setExplicitHealthCheckEnabled(DeviceConfig.getBoolean(
-                    DeviceConfig.NAMESPACE_ROLLBACK,
-                    PROPERTY_WATCHDOG_EXPLICIT_HEALTH_CHECK_ENABLED,
-                    DEFAULT_EXPLICIT_HEALTH_CHECK_ENABLED));
-        }
-    }
-
-    /**
-     * Persists mAllObservers to file. Threshold information is ignored.
-     */
-    private boolean saveToFile() {
-        Slog.i(TAG, "Saving observer state to file");
-        synchronized (sLock) {
-            FileOutputStream stream;
-            try {
-                stream = mPolicyFile.startWrite();
-            } catch (IOException e) {
-                Slog.w(TAG, "Cannot update monitored packages", e);
-                return false;
-            }
-
-            try {
-                XmlSerializer out = new FastXmlSerializer();
-                out.setOutput(stream, UTF_8.name());
-                out.startDocument(null, true);
-                out.startTag(null, TAG_PACKAGE_WATCHDOG);
-                out.attribute(null, ATTR_VERSION, Integer.toString(DB_VERSION));
-                for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
-                    mAllObservers.valueAt(oIndex).writeLocked(out);
-                }
-                out.endTag(null, TAG_PACKAGE_WATCHDOG);
-                out.endDocument();
-                mPolicyFile.finishWrite(stream);
-                return true;
-            } catch (IOException e) {
-                Slog.w(TAG, "Failed to save monitored packages, restoring backup", e);
-                mPolicyFile.failWrite(stream);
-                return false;
-            }
-        }
-    }
-
-    private void saveToFileAsync() {
-        if (!mLongTaskHandler.hasCallbacks(mSaveToFile)) {
-            mLongTaskHandler.post(mSaveToFile);
-        }
-    }
-
-    /** @hide Convert a {@code LongArrayQueue} to a String of comma-separated values. */
-    public static String longArrayQueueToString(LongArrayQueue queue) {
-        if (queue.size() > 0) {
-            StringBuilder sb = new StringBuilder();
-            sb.append(queue.get(0));
-            for (int i = 1; i < queue.size(); i++) {
-                sb.append(",");
-                sb.append(queue.get(i));
-            }
-            return sb.toString();
-        }
-        return "";
-    }
-
-    /** @hide Parse a comma-separated String of longs into a LongArrayQueue. */
-    public static LongArrayQueue parseLongArrayQueue(String commaSeparatedValues) {
-        LongArrayQueue result = new LongArrayQueue();
-        if (!TextUtils.isEmpty(commaSeparatedValues)) {
-            String[] values = commaSeparatedValues.split(",");
-            for (String value : values) {
-                result.addLast(Long.parseLong(value));
-            }
-        }
-        return result;
-    }
-
-
-    /** Dump status of every observer in mAllObservers. */
-    public void dump(@NonNull PrintWriter pw) {
-        if (Flags.synchronousRebootInRescueParty() && isRecoveryTriggeredReboot()) {
-            dumpInternal(pw);
-        } else {
-            synchronized (sLock) {
-                dumpInternal(pw);
-            }
-        }
-    }
-
-    /**
-     * Check if we're currently attempting to reboot during mitigation. This method must return
-     * true if triggered reboot early during a boot loop, since the device will not be fully booted
-     * at this time.
-     */
-    public static boolean isRecoveryTriggeredReboot() {
-        return isFactoryResetPropertySet() || isRebootPropertySet();
-    }
-
-    private static boolean isFactoryResetPropertySet() {
-        return CrashRecoveryProperties.attemptingFactoryReset().orElse(false);
-    }
-
-    private static boolean isRebootPropertySet() {
-        return CrashRecoveryProperties.attemptingReboot().orElse(false);
-    }
-
-    private void dumpInternal(@NonNull PrintWriter pw) {
-        IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
-        ipw.println("Package Watchdog status");
-        ipw.increaseIndent();
-        synchronized (sLock) {
-            for (String observerName : mAllObservers.keySet()) {
-                ipw.println("Observer name: " + observerName);
-                ipw.increaseIndent();
-                ObserverInternal observerInternal = mAllObservers.get(observerName);
-                observerInternal.dump(ipw);
-                ipw.decreaseIndent();
-            }
-        }
-        ipw.decreaseIndent();
-        dumpCrashRecoveryEvents(ipw);
-    }
-
-    @VisibleForTesting
-    @GuardedBy("sLock")
-    void registerObserverInternal(ObserverInternal observerInternal) {
-        mAllObservers.put(observerInternal.name, observerInternal);
-    }
-
-    /**
-     * Represents an observer monitoring a set of packages along with the failure thresholds for
-     * each package.
-     *
-     * <p> Note, the PackageWatchdog#sLock must always be held when reading or writing
-     * instances of this class.
-     */
-    static class ObserverInternal {
-        public final String name;
-        @GuardedBy("sLock")
-        private final ArrayMap<String, MonitoredPackage> mPackages = new ArrayMap<>();
-        @Nullable
-        @GuardedBy("sLock")
-        public PackageHealthObserver registeredObserver;
-        public Executor observerExecutor;
-        private int mMitigationCount;
-
-        ObserverInternal(String name, List<MonitoredPackage> packages) {
-            this(name, packages, /*mitigationCount=*/ 0);
-        }
-
-        ObserverInternal(String name, List<MonitoredPackage> packages, int mitigationCount) {
-            this.name = name;
-            updatePackagesLocked(packages);
-            this.mMitigationCount = mitigationCount;
-        }
-
-        /**
-         * Writes important {@link MonitoredPackage} details for this observer to file.
-         * Does not persist any package failure thresholds.
-         */
-        @GuardedBy("sLock")
-        public boolean writeLocked(XmlSerializer out) {
-            try {
-                out.startTag(null, TAG_OBSERVER);
-                out.attribute(null, ATTR_NAME, name);
-                if (Flags.recoverabilityDetection()) {
-                    out.attribute(null, ATTR_MITIGATION_COUNT, Integer.toString(mMitigationCount));
-                }
-                for (int i = 0; i < mPackages.size(); i++) {
-                    MonitoredPackage p = mPackages.valueAt(i);
-                    p.writeLocked(out);
-                }
-                out.endTag(null, TAG_OBSERVER);
-                return true;
-            } catch (IOException e) {
-                Slog.w(TAG, "Cannot save observer", e);
-                return false;
-            }
-        }
-
-        public int getBootMitigationCount() {
-            return mMitigationCount;
-        }
-
-        public void setBootMitigationCount(int mitigationCount) {
-            mMitigationCount = mitigationCount;
-        }
-
-        @GuardedBy("sLock")
-        public void updatePackagesLocked(List<MonitoredPackage> packages) {
-            for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
-                MonitoredPackage p = packages.get(pIndex);
-                MonitoredPackage existingPackage = getMonitoredPackage(p.getName());
-                if (existingPackage != null) {
-                    existingPackage.updateHealthCheckDuration(p.mDurationMs);
-                } else {
-                    putMonitoredPackage(p);
-                }
-            }
-        }
-
-        /**
-         * Reduces the monitoring durations of all packages observed by this observer by
-         * {@code elapsedMs}. If any duration is less than 0, the package is removed from
-         * observation. If any health check duration is less than 0, the health check result
-         * is evaluated.
-         *
-         * @return a {@link Set} of packages that were removed from the observer without explicit
-         * health check passing, or an empty list if no package expired for which an explicit health
-         * check was still pending
-         */
-        @GuardedBy("sLock")
-        private Set<MonitoredPackage> prunePackagesLocked(long elapsedMs) {
-            Set<MonitoredPackage> failedPackages = new ArraySet<>();
-            Iterator<MonitoredPackage> it = mPackages.values().iterator();
-            while (it.hasNext()) {
-                MonitoredPackage p = it.next();
-                int oldState = p.getHealthCheckStateLocked();
-                int newState = p.handleElapsedTimeLocked(elapsedMs);
-                if (oldState != HealthCheckState.FAILED
-                        && newState == HealthCheckState.FAILED) {
-                    Slog.i(TAG, "Package " + p.getName() + " failed health check");
-                    failedPackages.add(p);
-                }
-                if (p.isExpiredLocked()) {
-                    it.remove();
-                }
-            }
-            return failedPackages;
-        }
-
-        /**
-         * Increments failure counts of {@code packageName}.
-         * @returns {@code true} if failure threshold is exceeded, {@code false} otherwise
-         * @hide
-         */
-        @GuardedBy("sLock")
-        public boolean notifyPackageFailureLocked(String packageName) {
-            if (getMonitoredPackage(packageName) == null && registeredObserver.isPersistent()
-                    && registeredObserver.mayObservePackage(packageName)) {
-                putMonitoredPackage(sPackageWatchdog.newMonitoredPackage(
-                        packageName, DEFAULT_OBSERVING_DURATION_MS, false));
-            }
-            MonitoredPackage p = getMonitoredPackage(packageName);
-            if (p != null) {
-                return p.onFailureLocked();
-            }
-            return false;
-        }
-
-        /**
-         * Returns the map of packages monitored by this observer.
-         *
-         * @return a mapping of package names to {@link MonitoredPackage} objects.
-         */
-        @GuardedBy("sLock")
-        public ArrayMap<String, MonitoredPackage> getMonitoredPackages() {
-            return mPackages;
-        }
-
-        /**
-         * Returns the {@link MonitoredPackage} associated with a given package name if the
-         * package is being monitored by this observer.
-         *
-         * @param packageName: the name of the package.
-         * @return the {@link MonitoredPackage} object associated with the package name if one
-         *         exists, {@code null} otherwise.
-         */
-        @GuardedBy("sLock")
-        @Nullable
-        public MonitoredPackage getMonitoredPackage(String packageName) {
-            return mPackages.get(packageName);
-        }
-
-        /**
-         * Associates a {@link MonitoredPackage} with the observer.
-         *
-         * @param p: the {@link MonitoredPackage} to store.
-         */
-        @GuardedBy("sLock")
-        public void putMonitoredPackage(MonitoredPackage p) {
-            mPackages.put(p.getName(), p);
-        }
-
-        /**
-         * Returns one ObserverInternal from the {@code parser} and advances its state.
-         *
-         * <p>Note that this method is <b>not</b> thread safe. It should only be called from
-         * #loadFromFile which in turn is only called on construction of the
-         * singleton PackageWatchdog.
-         **/
-        public static ObserverInternal read(XmlPullParser parser, PackageWatchdog watchdog) {
-            String observerName = null;
-            int observerMitigationCount = 0;
-            if (TAG_OBSERVER.equals(parser.getName())) {
-                observerName = parser.getAttributeValue(null, ATTR_NAME);
-                if (TextUtils.isEmpty(observerName)) {
-                    Slog.wtf(TAG, "Unable to read observer name");
-                    return null;
-                }
-            }
-            List<MonitoredPackage> packages = new ArrayList<>();
-            int innerDepth = parser.getDepth();
-            try {
-                if (Flags.recoverabilityDetection()) {
-                    try {
-                        observerMitigationCount = Integer.parseInt(
-                                parser.getAttributeValue(null, ATTR_MITIGATION_COUNT));
-                    } catch (Exception e) {
-                        Slog.i(
-                            TAG,
-                            "ObserverInternal mitigation count was not present.");
-                    }
-                }
-                while (XmlUtils.nextElementWithin(parser, innerDepth)) {
-                    if (TAG_PACKAGE.equals(parser.getName())) {
-                        try {
-                            MonitoredPackage pkg = watchdog.parseMonitoredPackage(parser);
-                            if (pkg != null) {
-                                packages.add(pkg);
-                            }
-                        } catch (NumberFormatException e) {
-                            Slog.wtf(TAG, "Skipping package for observer " + observerName, e);
-                            continue;
-                        }
-                    }
-                }
-            } catch (XmlPullParserException | IOException e) {
-                Slog.wtf(TAG, "Unable to read observer " + observerName, e);
-                return null;
-            }
-            if (packages.isEmpty()) {
-                return null;
-            }
-            return new ObserverInternal(observerName, packages, observerMitigationCount);
-        }
-
-        /** Dumps information about this observer and the packages it watches. */
-        public void dump(IndentingPrintWriter pw) {
-            boolean isPersistent = registeredObserver != null && registeredObserver.isPersistent();
-            pw.println("Persistent: " + isPersistent);
-            for (String packageName : mPackages.keySet()) {
-                MonitoredPackage p = getMonitoredPackage(packageName);
-                pw.println(packageName +  ": ");
-                pw.increaseIndent();
-                pw.println("# Failures: " + p.mFailureHistory.size());
-                pw.println("Monitoring duration remaining: " + p.mDurationMs + "ms");
-                pw.println("Explicit health check duration: " + p.mHealthCheckDurationMs + "ms");
-                pw.println("Health check state: " + p.toString(p.mHealthCheckState));
-                pw.decreaseIndent();
-            }
-        }
-    }
-
-    /** @hide */
-    @Retention(SOURCE)
-    @IntDef(value = {
-            HealthCheckState.ACTIVE,
-            HealthCheckState.INACTIVE,
-            HealthCheckState.PASSED,
-            HealthCheckState.FAILED})
-    public @interface HealthCheckState {
-        // The package has not passed health check but has requested a health check
-        int ACTIVE = 0;
-        // The package has not passed health check and has not requested a health check
-        int INACTIVE = 1;
-        // The package has passed health check
-        int PASSED = 2;
-        // The package has failed health check
-        int FAILED = 3;
-    }
-
-    MonitoredPackage newMonitoredPackage(
-            String name, long durationMs, boolean hasPassedHealthCheck) {
-        return newMonitoredPackage(name, durationMs, Long.MAX_VALUE, hasPassedHealthCheck,
-                new LongArrayQueue());
-    }
-
-    MonitoredPackage newMonitoredPackage(String name, long durationMs, long healthCheckDurationMs,
-            boolean hasPassedHealthCheck, LongArrayQueue mitigationCalls) {
-        return new MonitoredPackage(name, durationMs, healthCheckDurationMs,
-                hasPassedHealthCheck, mitigationCalls);
-    }
-
-    MonitoredPackage parseMonitoredPackage(XmlPullParser parser)
-            throws XmlPullParserException {
-        String packageName = parser.getAttributeValue(null, ATTR_NAME);
-        long duration = Long.parseLong(parser.getAttributeValue(null, ATTR_DURATION));
-        long healthCheckDuration = Long.parseLong(parser.getAttributeValue(null,
-                ATTR_EXPLICIT_HEALTH_CHECK_DURATION));
-        boolean hasPassedHealthCheck = Boolean.parseBoolean(parser.getAttributeValue(null,
-                ATTR_PASSED_HEALTH_CHECK));
-        LongArrayQueue mitigationCalls = parseLongArrayQueue(
-                parser.getAttributeValue(null, ATTR_MITIGATION_CALLS));
-        return newMonitoredPackage(packageName,
-                duration, healthCheckDuration, hasPassedHealthCheck, mitigationCalls);
-    }
-
-    /**
-     * Represents a package and its health check state along with the time
-     * it should be monitored for.
-     *
-     * <p> Note, the PackageWatchdog#sLock must always be held when reading or writing
-     * instances of this class.
-     */
-    class MonitoredPackage {
-        private final String mPackageName;
-        // Times when package failures happen sorted in ascending order
-        @GuardedBy("sLock")
-        private final LongArrayQueue mFailureHistory = new LongArrayQueue();
-        // Times when an observer was called to mitigate this package's failure. Sorted in
-        // ascending order.
-        @GuardedBy("sLock")
-        private final LongArrayQueue mMitigationCalls;
-        // One of STATE_[ACTIVE|INACTIVE|PASSED|FAILED]. Updated on construction and after
-        // methods that could change the health check state: handleElapsedTimeLocked and
-        // tryPassHealthCheckLocked
-        private int mHealthCheckState = HealthCheckState.INACTIVE;
-        // Whether an explicit health check has passed.
-        // This value in addition with mHealthCheckDurationMs determines the health check state
-        // of the package, see #getHealthCheckStateLocked
-        @GuardedBy("sLock")
-        private boolean mHasPassedHealthCheck;
-        // System uptime duration to monitor package.
-        @GuardedBy("sLock")
-        private long mDurationMs;
-        // System uptime duration to check the result of an explicit health check
-        // Initially, MAX_VALUE until we get a value from the health check service
-        // and request health checks.
-        // This value in addition with mHasPassedHealthCheck determines the health check state
-        // of the package, see #getHealthCheckStateLocked
-        @GuardedBy("sLock")
-        private long mHealthCheckDurationMs = Long.MAX_VALUE;
-
-        MonitoredPackage(String packageName, long durationMs,
-                long healthCheckDurationMs, boolean hasPassedHealthCheck,
-                LongArrayQueue mitigationCalls) {
-            mPackageName = packageName;
-            mDurationMs = durationMs;
-            mHealthCheckDurationMs = healthCheckDurationMs;
-            mHasPassedHealthCheck = hasPassedHealthCheck;
-            mMitigationCalls = mitigationCalls;
-            updateHealthCheckStateLocked();
-        }
-
-        /** Writes the salient fields to disk using {@code out}.
-         * @hide
-         */
-        @GuardedBy("sLock")
-        public void writeLocked(XmlSerializer out) throws IOException {
-            out.startTag(null, TAG_PACKAGE);
-            out.attribute(null, ATTR_NAME, getName());
-            out.attribute(null, ATTR_DURATION, Long.toString(mDurationMs));
-            out.attribute(null, ATTR_EXPLICIT_HEALTH_CHECK_DURATION,
-                    Long.toString(mHealthCheckDurationMs));
-            out.attribute(null, ATTR_PASSED_HEALTH_CHECK, Boolean.toString(mHasPassedHealthCheck));
-            LongArrayQueue normalizedCalls = normalizeMitigationCalls();
-            out.attribute(null, ATTR_MITIGATION_CALLS, longArrayQueueToString(normalizedCalls));
-            out.endTag(null, TAG_PACKAGE);
-        }
-
-        /**
-         * Increment package failures or resets failure count depending on the last package failure.
-         *
-         * @return {@code true} if failure count exceeds a threshold, {@code false} otherwise
-         */
-        @GuardedBy("sLock")
-        public boolean onFailureLocked() {
-            // Sliding window algorithm: find out if there exists a window containing failures >=
-            // mTriggerFailureCount.
-            final long now = mSystemClock.uptimeMillis();
-            mFailureHistory.addLast(now);
-            while (now - mFailureHistory.peekFirst() > mTriggerFailureDurationMs) {
-                // Prune values falling out of the window
-                mFailureHistory.removeFirst();
-            }
-            boolean failed = mFailureHistory.size() >= mTriggerFailureCount;
-            if (failed) {
-                mFailureHistory.clear();
-            }
-            return failed;
-        }
-
-        /**
-         * Notes the timestamp of a mitigation call into the observer.
-         */
-        @GuardedBy("sLock")
-        public void noteMitigationCallLocked() {
-            mMitigationCalls.addLast(mSystemClock.uptimeMillis());
-        }
-
-        /**
-         * Prunes any mitigation calls outside of the de-escalation window, and returns the
-         * number of calls that are in the window afterwards.
-         *
-         * @return the number of mitigation calls made in the de-escalation window.
-         */
-        @GuardedBy("sLock")
-        public int getMitigationCountLocked() {
-            try {
-                final long now = mSystemClock.uptimeMillis();
-                while (now - mMitigationCalls.peekFirst() > DEFAULT_DEESCALATION_WINDOW_MS) {
-                    mMitigationCalls.removeFirst();
-                }
-            } catch (NoSuchElementException ignore) {
-            }
-
-            return mMitigationCalls.size();
-        }
-
-        /**
-         * Before writing to disk, make the mitigation call timestamps relative to the current
-         * system uptime. This is because they need to be relative to the uptime which will reset
-         * at the next boot.
-         *
-         * @return a LongArrayQueue of the mitigation calls relative to the current system uptime.
-         */
-        @GuardedBy("sLock")
-        public LongArrayQueue normalizeMitigationCalls() {
-            LongArrayQueue normalized = new LongArrayQueue();
-            final long now = mSystemClock.uptimeMillis();
-            for (int i = 0; i < mMitigationCalls.size(); i++) {
-                normalized.addLast(mMitigationCalls.get(i) - now);
-            }
-            return normalized;
-        }
-
-        /**
-         * Sets the initial health check duration.
-         *
-         * @return the new health check state
-         */
-        @GuardedBy("sLock")
-        public int setHealthCheckActiveLocked(long initialHealthCheckDurationMs) {
-            if (initialHealthCheckDurationMs <= 0) {
-                Slog.wtf(TAG, "Cannot set non-positive health check duration "
-                        + initialHealthCheckDurationMs + "ms for package " + getName()
-                        + ". Using total duration " + mDurationMs + "ms instead");
-                initialHealthCheckDurationMs = mDurationMs;
-            }
-            if (mHealthCheckState == HealthCheckState.INACTIVE) {
-                // Transitions to ACTIVE
-                mHealthCheckDurationMs = initialHealthCheckDurationMs;
-            }
-            return updateHealthCheckStateLocked();
-        }
-
-        /**
-         * Updates the monitoring durations of the package.
-         *
-         * @return the new health check state
-         */
-        @GuardedBy("sLock")
-        public int handleElapsedTimeLocked(long elapsedMs) {
-            if (elapsedMs <= 0) {
-                Slog.w(TAG, "Cannot handle non-positive elapsed time for package " + getName());
-                return mHealthCheckState;
-            }
-            // Transitions to FAILED if now <= 0 and health check not passed
-            mDurationMs -= elapsedMs;
-            if (mHealthCheckState == HealthCheckState.ACTIVE) {
-                // We only update health check durations if we have #setHealthCheckActiveLocked
-                // This ensures we don't leave the INACTIVE state for an unexpected elapsed time
-                // Transitions to FAILED if now <= 0 and health check not passed
-                mHealthCheckDurationMs -= elapsedMs;
-            }
-            return updateHealthCheckStateLocked();
-        }
-
-        /** Explicitly update the monitoring duration of the package. */
-        @GuardedBy("sLock")
-        public void updateHealthCheckDuration(long newDurationMs) {
-            mDurationMs = newDurationMs;
-        }
-
-        /**
-         * Marks the health check as passed and transitions to {@link HealthCheckState.PASSED}
-         * if not yet {@link HealthCheckState.FAILED}.
-         *
-         * @return the new {@link HealthCheckState health check state}
-         */
-        @GuardedBy("sLock")
-        @HealthCheckState
-        public int tryPassHealthCheckLocked() {
-            if (mHealthCheckState != HealthCheckState.FAILED) {
-                // FAILED is a final state so only pass if we haven't failed
-                // Transition to PASSED
-                mHasPassedHealthCheck = true;
-            }
-            return updateHealthCheckStateLocked();
-        }
-
-        /** Returns the monitored package name. */
-        private String getName() {
-            return mPackageName;
-        }
-
-        /**
-         * Returns the current {@link HealthCheckState health check state}.
-         */
-        @GuardedBy("sLock")
-        @HealthCheckState
-        public int getHealthCheckStateLocked() {
-            return mHealthCheckState;
-        }
-
-        /**
-         * Returns the shortest duration before the package should be scheduled for a prune.
-         *
-         * @return the duration or {@link Long#MAX_VALUE} if the package should not be scheduled
-         */
-        @GuardedBy("sLock")
-        public long getShortestScheduleDurationMsLocked() {
-            // Consider health check duration only if #isPendingHealthChecksLocked is true
-            return Math.min(toPositive(mDurationMs),
-                    isPendingHealthChecksLocked()
-                    ? toPositive(mHealthCheckDurationMs) : Long.MAX_VALUE);
-        }
-
-        /**
-         * Returns {@code true} if the total duration left to monitor the package is less than or
-         * equal to 0 {@code false} otherwise.
-         */
-        @GuardedBy("sLock")
-        public boolean isExpiredLocked() {
-            return mDurationMs <= 0;
-        }
-
-        /**
-         * Returns {@code true} if the package, {@link #getName} is expecting health check results
-         * {@code false} otherwise.
-         */
-        @GuardedBy("sLock")
-        public boolean isPendingHealthChecksLocked() {
-            return mHealthCheckState == HealthCheckState.ACTIVE
-                    || mHealthCheckState == HealthCheckState.INACTIVE;
-        }
-
-        /**
-         * Updates the health check state based on {@link #mHasPassedHealthCheck}
-         * and {@link #mHealthCheckDurationMs}.
-         *
-         * @return the new {@link HealthCheckState health check state}
-         */
-        @GuardedBy("sLock")
-        @HealthCheckState
-        private int updateHealthCheckStateLocked() {
-            int oldState = mHealthCheckState;
-            if (mHasPassedHealthCheck) {
-                // Set final state first to avoid ambiguity
-                mHealthCheckState = HealthCheckState.PASSED;
-            } else if (mHealthCheckDurationMs <= 0 || mDurationMs <= 0) {
-                // Set final state first to avoid ambiguity
-                mHealthCheckState = HealthCheckState.FAILED;
-            } else if (mHealthCheckDurationMs == Long.MAX_VALUE) {
-                mHealthCheckState = HealthCheckState.INACTIVE;
-            } else {
-                mHealthCheckState = HealthCheckState.ACTIVE;
-            }
-
-            if (oldState != mHealthCheckState) {
-                Slog.i(TAG, "Updated health check state for package " + getName() + ": "
-                        + toString(oldState) + " -> " + toString(mHealthCheckState));
-            }
-            return mHealthCheckState;
-        }
-
-        /** Returns a {@link String} representation of the current health check state. */
-        private String toString(@HealthCheckState int state) {
-            switch (state) {
-                case HealthCheckState.ACTIVE:
-                    return "ACTIVE";
-                case HealthCheckState.INACTIVE:
-                    return "INACTIVE";
-                case HealthCheckState.PASSED:
-                    return "PASSED";
-                case HealthCheckState.FAILED:
-                    return "FAILED";
-                default:
-                    return "UNKNOWN";
-            }
-        }
-
-        /** Returns {@code value} if it is greater than 0 or {@link Long#MAX_VALUE} otherwise. */
-        private long toPositive(long value) {
-            return value > 0 ? value : Long.MAX_VALUE;
-        }
-
-        /** Compares the equality of this object with another {@link MonitoredPackage}. */
-        @VisibleForTesting
-        boolean isEqualTo(MonitoredPackage pkg) {
-            return (getName().equals(pkg.getName()))
-                    && mDurationMs == pkg.mDurationMs
-                    && mHasPassedHealthCheck == pkg.mHasPassedHealthCheck
-                    && mHealthCheckDurationMs == pkg.mHealthCheckDurationMs
-                    && (mMitigationCalls.toString()).equals(pkg.mMitigationCalls.toString());
-        }
-    }
-
-    @GuardedBy("sLock")
-    @SuppressWarnings("GuardedBy")
-    void saveAllObserversBootMitigationCountToMetadata(String filePath) {
-        HashMap<String, Integer> bootMitigationCounts = new HashMap<>();
-        for (int i = 0; i < mAllObservers.size(); i++) {
-            final ObserverInternal observer = mAllObservers.valueAt(i);
-            bootMitigationCounts.put(observer.name, observer.getBootMitigationCount());
-        }
-
-        FileOutputStream fileStream = null;
-        ObjectOutputStream objectStream = null;
-        try {
-            fileStream = new FileOutputStream(new File(filePath));
-            objectStream = new ObjectOutputStream(fileStream);
-            objectStream.writeObject(bootMitigationCounts);
-            objectStream.flush();
-        } catch (Exception e) {
-            Slog.i(TAG, "Could not save observers metadata to file: " + e);
-            return;
-        } finally {
-            IoUtils.closeQuietly(objectStream);
-            IoUtils.closeQuietly(fileStream);
-        }
-    }
-
-    /**
-     * Handles the thresholding logic for system server boots.
-     */
-    class BootThreshold {
-
-        private final int mBootTriggerCount;
-        private final long mTriggerWindow;
-
-        BootThreshold(int bootTriggerCount, long triggerWindow) {
-            this.mBootTriggerCount = bootTriggerCount;
-            this.mTriggerWindow = triggerWindow;
-        }
-
-        public void reset() {
-            setStart(0);
-            setCount(0);
-        }
-
-        protected int getCount() {
-            return CrashRecoveryProperties.rescueBootCount().orElse(0);
-        }
-
-        protected void setCount(int count) {
-            CrashRecoveryProperties.rescueBootCount(count);
-        }
-
-        public long getStart() {
-            return CrashRecoveryProperties.rescueBootStart().orElse(0L);
-        }
-
-        public int getMitigationCount() {
-            return CrashRecoveryProperties.bootMitigationCount().orElse(0);
-        }
-
-        public void setStart(long start) {
-            CrashRecoveryProperties.rescueBootStart(getStartTime(start));
-        }
-
-        public void setMitigationStart(long start) {
-            CrashRecoveryProperties.bootMitigationStart(getStartTime(start));
-        }
-
-        public long getMitigationStart() {
-            return CrashRecoveryProperties.bootMitigationStart().orElse(0L);
-        }
-
-        public void setMitigationCount(int count) {
-            CrashRecoveryProperties.bootMitigationCount(count);
-        }
-
-        private static long constrain(long amount, long low, long high) {
-            return amount < low ? low : (amount > high ? high : amount);
-        }
-
-        public long getStartTime(long start) {
-            final long now = mSystemClock.uptimeMillis();
-            return constrain(start, 0, now);
-        }
-
-        public void saveMitigationCountToMetadata() {
-            try (BufferedWriter writer = new BufferedWriter(new FileWriter(METADATA_FILE))) {
-                writer.write(String.valueOf(getMitigationCount()));
-            } catch (Exception e) {
-                Slog.e(TAG, "Could not save metadata to file: " + e);
-            }
-        }
-
-        public void readMitigationCountFromMetadataIfNecessary() {
-            File bootPropsFile = new File(METADATA_FILE);
-            if (bootPropsFile.exists()) {
-                try (BufferedReader reader = new BufferedReader(new FileReader(METADATA_FILE))) {
-                    String mitigationCount = reader.readLine();
-                    setMitigationCount(Integer.parseInt(mitigationCount));
-                    bootPropsFile.delete();
-                } catch (Exception e) {
-                    Slog.i(TAG, "Could not read metadata file: " + e);
-                }
-            }
-        }
-
-
-        /** Increments the boot counter, and returns whether the device is bootlooping. */
-        @GuardedBy("sLock")
-        public boolean incrementAndTest() {
-            if (Flags.recoverabilityDetection()) {
-                readAllObserversBootMitigationCountIfNecessary(METADATA_FILE);
-            } else {
-                readMitigationCountFromMetadataIfNecessary();
-            }
-
-            final long now = mSystemClock.uptimeMillis();
-            if (now - getStart() < 0) {
-                Slog.e(TAG, "Window was less than zero. Resetting start to current time.");
-                setStart(now);
-                setMitigationStart(now);
-            }
-            if (now - getMitigationStart() > DEFAULT_DEESCALATION_WINDOW_MS) {
-                setMitigationStart(now);
-                if (Flags.recoverabilityDetection()) {
-                    resetAllObserversBootMitigationCount();
-                } else {
-                    setMitigationCount(0);
-                }
-            }
-            final long window = now - getStart();
-            if (window >= mTriggerWindow) {
-                setCount(1);
-                setStart(now);
-                return false;
-            } else {
-                int count = getCount() + 1;
-                setCount(count);
-                EventLog.writeEvent(LOG_TAG_RESCUE_NOTE, Process.ROOT_UID, count, window);
-                if (Flags.recoverabilityDetection()) {
-                    // After a reboot (e.g. by WARM_REBOOT or mainline rollback) we apply
-                    // mitigations without waiting for DEFAULT_BOOT_LOOP_TRIGGER_COUNT.
-                    return (count >= mBootTriggerCount)
-                            || (performedMitigationsDuringWindow() && count > 1);
-                }
-                return count >= mBootTriggerCount;
-            }
-        }
-
-        @GuardedBy("sLock")
-        private boolean performedMitigationsDuringWindow() {
-            for (ObserverInternal observerInternal: mAllObservers.values()) {
-                if (observerInternal.getBootMitigationCount() > 0) {
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        @GuardedBy("sLock")
-        private void resetAllObserversBootMitigationCount() {
-            for (int i = 0; i < mAllObservers.size(); i++) {
-                final ObserverInternal observer = mAllObservers.valueAt(i);
-                observer.setBootMitigationCount(0);
-            }
-            saveAllObserversBootMitigationCountToMetadata(METADATA_FILE);
-        }
-
-        @GuardedBy("sLock")
-        @SuppressWarnings("GuardedBy")
-        void readAllObserversBootMitigationCountIfNecessary(String filePath) {
-            File metadataFile = new File(filePath);
-            if (metadataFile.exists()) {
-                FileInputStream fileStream = null;
-                ObjectInputStream objectStream = null;
-                HashMap<String, Integer> bootMitigationCounts = null;
-                try {
-                    fileStream = new FileInputStream(metadataFile);
-                    objectStream = new ObjectInputStream(fileStream);
-                    bootMitigationCounts =
-                            (HashMap<String, Integer>) objectStream.readObject();
-                } catch (Exception e) {
-                    Slog.i(TAG, "Could not read observer metadata file: " + e);
-                   return;
-                } finally {
-                    IoUtils.closeQuietly(objectStream);
-                    IoUtils.closeQuietly(fileStream);
-                }
-
-                if (bootMitigationCounts == null || bootMitigationCounts.isEmpty()) {
-                    Slog.i(TAG, "No observer in metadata file");
-                    return;
-                }
-                for (int i = 0; i < mAllObservers.size(); i++) {
-                    final ObserverInternal observer = mAllObservers.valueAt(i);
-                    if (bootMitigationCounts.containsKey(observer.name)) {
-                        observer.setBootMitigationCount(
-                                bootMitigationCounts.get(observer.name));
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * Register broadcast receiver for shutdown.
-     * We would save the observer state to persist across boots.
-     *
-     * @hide
-     */
-    public void registerShutdownBroadcastReceiver() {
-        BroadcastReceiver shutdownEventReceiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                // Only write if intent is relevant to device reboot or shutdown.
-                String intentAction = intent.getAction();
-                if (ACTION_REBOOT.equals(intentAction)
-                        || ACTION_SHUTDOWN.equals(intentAction)) {
-                    writeNow();
-                }
-            }
-        };
-
-        // Setup receiver for device reboots or shutdowns.
-        IntentFilter filter = new IntentFilter(ACTION_REBOOT);
-        filter.addAction(ACTION_SHUTDOWN);
-        mContext.registerReceiverForAllUsers(shutdownEventReceiver, filter, null,
-                /* run on main thread */ null);
-    }
-}
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java
deleted file mode 100644
index 40bc5f7..0000000
--- a/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java
+++ /dev/null
@@ -1,993 +0,0 @@
-/*
- * Copyright (C) 2017 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;
-
-import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SKIPPED;
-import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SUCCESS;
-import static com.android.server.crashrecovery.CrashRecoveryUtils.logCrashRecoveryEvent;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.VersionedPackage;
-import android.crashrecovery.flags.Flags;
-import android.os.Build;
-import android.os.Environment;
-import android.os.PowerManager;
-import android.os.RecoverySystem;
-import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.provider.DeviceConfig;
-import android.provider.Settings;
-import android.sysprop.CrashRecoveryProperties;
-import android.text.TextUtils;
-import android.util.ArraySet;
-import android.util.ArrayUtils;
-import android.util.EventLog;
-import android.util.FileUtils;
-import android.util.Log;
-import android.util.Slog;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.PackageWatchdog.FailureReasons;
-import com.android.server.PackageWatchdog.PackageHealthObserver;
-import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
-import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog;
-
-import java.io.File;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Utilities to help rescue the system from crash loops. Callers are expected to
- * report boot events and persistent app crashes, and if they happen frequently
- * enough this class will slowly escalate through several rescue operations
- * before finally rebooting and prompting the user if they want to wipe data as
- * a last resort.
- *
- * @hide
- */
-public class RescueParty {
-    @VisibleForTesting
-    static final String PROP_ENABLE_RESCUE = "persist.sys.enable_rescue";
-    @VisibleForTesting
-    static final int LEVEL_NONE = 0;
-    @VisibleForTesting
-    static final int LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS = 1;
-    @VisibleForTesting
-    static final int LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES = 2;
-    @VisibleForTesting
-    static final int LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS = 3;
-    @VisibleForTesting
-    static final int LEVEL_WARM_REBOOT = 4;
-    @VisibleForTesting
-    static final int LEVEL_FACTORY_RESET = 5;
-    @VisibleForTesting
-    static final int RESCUE_LEVEL_NONE = 0;
-    @VisibleForTesting
-    static final int RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET = 1;
-    @VisibleForTesting
-    static final int RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET = 2;
-    @VisibleForTesting
-    static final int RESCUE_LEVEL_WARM_REBOOT = 3;
-    @VisibleForTesting
-    static final int RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS = 4;
-    @VisibleForTesting
-    static final int RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES = 5;
-    @VisibleForTesting
-    static final int RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS = 6;
-    @VisibleForTesting
-    static final int RESCUE_LEVEL_FACTORY_RESET = 7;
-
-    @IntDef(prefix = { "RESCUE_LEVEL_" }, value = {
-        RESCUE_LEVEL_NONE,
-        RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET,
-        RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET,
-        RESCUE_LEVEL_WARM_REBOOT,
-        RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS,
-        RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES,
-        RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS,
-        RESCUE_LEVEL_FACTORY_RESET
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    @interface RescueLevels {}
-
-    @VisibleForTesting
-    static final String RESCUE_NON_REBOOT_LEVEL_LIMIT = "persist.sys.rescue_non_reboot_level_limit";
-    @VisibleForTesting
-    static final int DEFAULT_RESCUE_NON_REBOOT_LEVEL_LIMIT = RESCUE_LEVEL_WARM_REBOOT - 1;
-    @VisibleForTesting
-    static final String TAG = "RescueParty";
-    @VisibleForTesting
-    static final long DEFAULT_OBSERVING_DURATION_MS = TimeUnit.DAYS.toMillis(2);
-    @VisibleForTesting
-    static final int DEVICE_CONFIG_RESET_MODE = Settings.RESET_MODE_TRUSTED_DEFAULTS;
-    // The DeviceConfig namespace containing all RescueParty switches.
-    @VisibleForTesting
-    static final String NAMESPACE_CONFIGURATION = "configuration";
-    @VisibleForTesting
-    static final String NAMESPACE_TO_PACKAGE_MAPPING_FLAG =
-            "namespace_to_package_mapping";
-    @VisibleForTesting
-    static final long DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN = 1440;
-
-    private static final String NAME = "rescue-party-observer";
-
-    private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue";
-    private static final String PROP_VIRTUAL_DEVICE = "ro.hardware.virtual_device";
-    private static final String PROP_DEVICE_CONFIG_DISABLE_FLAG =
-            "persist.device_config.configuration.disable_rescue_party";
-    private static final String PROP_DISABLE_FACTORY_RESET_FLAG =
-            "persist.device_config.configuration.disable_rescue_party_factory_reset";
-    private static final String PROP_THROTTLE_DURATION_MIN_FLAG =
-            "persist.device_config.configuration.rescue_party_throttle_duration_min";
-
-    private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
-            | ApplicationInfo.FLAG_SYSTEM;
-
-    /**
-     * EventLog tags used when logging into the event log. Note the values must be sync with
-     * frameworks/base/services/core/java/com/android/server/EventLogTags.logtags to get correct
-     * name translation.
-     */
-    private static final int LOG_TAG_RESCUE_SUCCESS = 2902;
-    private static final int LOG_TAG_RESCUE_FAILURE = 2903;
-
-    /** Register the Rescue Party observer as a Package Watchdog health observer */
-    public static void registerHealthObserver(Context context) {
-        PackageWatchdog.getInstance(context).registerHealthObserver(
-                context.getMainExecutor(), RescuePartyObserver.getInstance(context));
-    }
-
-    private static boolean isDisabled() {
-        // Check if we're explicitly enabled for testing
-        if (SystemProperties.getBoolean(PROP_ENABLE_RESCUE, false)) {
-            return false;
-        }
-
-        // We're disabled if the DeviceConfig disable flag is set to true.
-        // This is in case that an emergency rollback of the feature is needed.
-        if (SystemProperties.getBoolean(PROP_DEVICE_CONFIG_DISABLE_FLAG, false)) {
-            Slog.v(TAG, "Disabled because of DeviceConfig flag");
-            return true;
-        }
-
-        // We're disabled on all engineering devices
-        if (Build.TYPE.equals("eng")) {
-            Slog.v(TAG, "Disabled because of eng build");
-            return true;
-        }
-
-        // We're disabled on userdebug devices connected over USB, since that's
-        // a decent signal that someone is actively trying to debug the device,
-        // or that it's in a lab environment.
-        if (Build.TYPE.equals("userdebug") && isUsbActive()) {
-            Slog.v(TAG, "Disabled because of active USB connection");
-            return true;
-        }
-
-        // One last-ditch check
-        if (SystemProperties.getBoolean(PROP_DISABLE_RESCUE, false)) {
-            Slog.v(TAG, "Disabled because of manual property");
-            return true;
-        }
-
-        return false;
-    }
-
-    /**
-     * Check if we're currently attempting to reboot for a factory reset. This method must
-     * return true if RescueParty tries to reboot early during a boot loop, since the device
-     * will not be fully booted at this time.
-     */
-    public static boolean isRecoveryTriggeredReboot() {
-        return isFactoryResetPropertySet() || isRebootPropertySet();
-    }
-
-    static boolean isFactoryResetPropertySet() {
-        return CrashRecoveryProperties.attemptingFactoryReset().orElse(false);
-    }
-
-    static boolean isRebootPropertySet() {
-        return CrashRecoveryProperties.attemptingReboot().orElse(false);
-    }
-
-    protected static long getLastFactoryResetTimeMs() {
-        return CrashRecoveryProperties.lastFactoryResetTimeMs().orElse(0L);
-    }
-
-    protected static int getMaxRescueLevelAttempted() {
-        return CrashRecoveryProperties.maxRescueLevelAttempted().orElse(LEVEL_NONE);
-    }
-
-    protected static void setFactoryResetProperty(boolean value) {
-        CrashRecoveryProperties.attemptingFactoryReset(value);
-    }
-    protected static void setRebootProperty(boolean value) {
-        CrashRecoveryProperties.attemptingReboot(value);
-    }
-
-    protected static void setLastFactoryResetTimeMs(long value) {
-        CrashRecoveryProperties.lastFactoryResetTimeMs(value);
-    }
-
-    protected static void setMaxRescueLevelAttempted(int level) {
-        CrashRecoveryProperties.maxRescueLevelAttempted(level);
-    }
-
-    private static Set<String> getPresetNamespacesForPackages(List<String> packageNames) {
-        Set<String> resultSet = new ArraySet<String>();
-        if (!Flags.deprecateFlagsAndSettingsResets()) {
-            try {
-                String flagVal = DeviceConfig.getString(NAMESPACE_CONFIGURATION,
-                        NAMESPACE_TO_PACKAGE_MAPPING_FLAG, "");
-                String[] mappingEntries = flagVal.split(",");
-                for (int i = 0; i < mappingEntries.length; i++) {
-                    if (TextUtils.isEmpty(mappingEntries[i])) {
-                        continue;
-                    }
-                    String[] splitEntry = mappingEntries[i].split(":");
-                    if (splitEntry.length != 2) {
-                        throw new RuntimeException("Invalid mapping entry: " + mappingEntries[i]);
-                    }
-                    String namespace = splitEntry[0];
-                    String packageName = splitEntry[1];
-
-                    if (packageNames.contains(packageName)) {
-                        resultSet.add(namespace);
-                    }
-                }
-            } catch (Exception e) {
-                resultSet.clear();
-                Slog.e(TAG, "Failed to read preset package to namespaces mapping.", e);
-            } finally {
-                return resultSet;
-            }
-        } else {
-            return resultSet;
-        }
-    }
-
-    @VisibleForTesting
-    static long getElapsedRealtime() {
-        return SystemClock.elapsedRealtime();
-    }
-
-    private static class RescuePartyMonitorCallback implements DeviceConfig.MonitorCallback {
-        Context mContext;
-
-        RescuePartyMonitorCallback(Context context) {
-            this.mContext = context;
-        }
-
-        public void onNamespaceUpdate(@NonNull String updatedNamespace) {
-            if (!Flags.deprecateFlagsAndSettingsResets()) {
-                startObservingPackages(mContext, updatedNamespace);
-            }
-        }
-
-        public void onDeviceConfigAccess(@NonNull String callingPackage,
-                @NonNull String namespace) {
-
-            if (!Flags.deprecateFlagsAndSettingsResets()) {
-                RescuePartyObserver.getInstance(mContext).recordDeviceConfigAccess(
-                        callingPackage,
-                        namespace);
-            }
-        }
-    }
-
-    private static void startObservingPackages(Context context, @NonNull String updatedNamespace) {
-        if (!Flags.deprecateFlagsAndSettingsResets()) {
-            RescuePartyObserver rescuePartyObserver = RescuePartyObserver.getInstance(context);
-            Set<String> callingPackages = rescuePartyObserver.getCallingPackagesSet(
-                    updatedNamespace);
-            if (callingPackages == null) {
-                return;
-            }
-            List<String> callingPackageList = new ArrayList<>();
-            callingPackageList.addAll(callingPackages);
-            Slog.i(TAG, "Starting to observe: " + callingPackageList + ", updated namespace: "
-                    + updatedNamespace);
-            PackageWatchdog.getInstance(context).startExplicitHealthCheck(
-                    callingPackageList,
-                    DEFAULT_OBSERVING_DURATION_MS,
-                    rescuePartyObserver);
-        }
-    }
-
-    private static int getMaxRescueLevel(boolean mayPerformReboot) {
-        if (Flags.recoverabilityDetection()) {
-            if (!mayPerformReboot
-                    || SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)) {
-                return SystemProperties.getInt(RESCUE_NON_REBOOT_LEVEL_LIMIT,
-                        DEFAULT_RESCUE_NON_REBOOT_LEVEL_LIMIT);
-            }
-            return RESCUE_LEVEL_FACTORY_RESET;
-        } else {
-            if (!mayPerformReboot
-                    || SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)) {
-                return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS;
-            }
-            return LEVEL_FACTORY_RESET;
-        }
-    }
-
-    private static int getMaxRescueLevel() {
-        if (!SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)) {
-            return Level.factoryReset();
-        }
-        return Level.reboot();
-    }
-
-    /**
-     * Get the rescue level to perform if this is the n-th attempt at mitigating failure.
-     *
-     * @param mitigationCount: the mitigation attempt number (1 = first attempt etc.)
-     * @param mayPerformReboot: whether or not a reboot and factory reset may be performed
-     *                          for the given failure.
-     * @return the rescue level for the n-th mitigation attempt.
-     */
-    private static int getRescueLevel(int mitigationCount, boolean mayPerformReboot) {
-        if (!Flags.deprecateFlagsAndSettingsResets()) {
-            if (mitigationCount == 1) {
-                return LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS;
-            } else if (mitigationCount == 2) {
-                return LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES;
-            } else if (mitigationCount == 3) {
-                return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS;
-            } else if (mitigationCount == 4) {
-                return Math.min(getMaxRescueLevel(mayPerformReboot), LEVEL_WARM_REBOOT);
-            } else if (mitigationCount >= 5) {
-                return Math.min(getMaxRescueLevel(mayPerformReboot), LEVEL_FACTORY_RESET);
-            } else {
-                Slog.w(TAG, "Expected positive mitigation count, was " + mitigationCount);
-                return LEVEL_NONE;
-            }
-        } else {
-            if (mitigationCount == 1) {
-                return Level.reboot();
-            } else if (mitigationCount >= 2) {
-                return Math.min(getMaxRescueLevel(), Level.factoryReset());
-            } else {
-                Slog.w(TAG, "Expected positive mitigation count, was " + mitigationCount);
-                return LEVEL_NONE;
-            }
-        }
-    }
-
-    /**
-     * Get the rescue level to perform if this is the n-th attempt at mitigating failure.
-     * When failedPackage is null then 1st and 2nd mitigation counts are redundant (scoped and
-     * all device config reset). Behaves as if one mitigation attempt was already done.
-     *
-     * @param mitigationCount the mitigation attempt number (1 = first attempt etc.).
-     * @param mayPerformReboot whether or not a reboot and factory reset may be performed
-     * for the given failure.
-     * @param failedPackage in case of bootloop this is null.
-     * @return the rescue level for the n-th mitigation attempt.
-     */
-    private static @RescueLevels int getRescueLevel(int mitigationCount, boolean mayPerformReboot,
-            @Nullable VersionedPackage failedPackage) {
-        // Skipping RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET since it's not defined without a failed
-        // package.
-        if (failedPackage == null && mitigationCount > 0) {
-            mitigationCount += 1;
-        }
-        if (mitigationCount == 1) {
-            return RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET;
-        } else if (mitigationCount == 2) {
-            return RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET;
-        } else if (mitigationCount == 3) {
-            return Math.min(getMaxRescueLevel(mayPerformReboot), RESCUE_LEVEL_WARM_REBOOT);
-        } else if (mitigationCount == 4) {
-            return Math.min(getMaxRescueLevel(mayPerformReboot),
-                    RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS);
-        } else if (mitigationCount == 5) {
-            return Math.min(getMaxRescueLevel(mayPerformReboot),
-                    RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES);
-        } else if (mitigationCount == 6) {
-            return Math.min(getMaxRescueLevel(mayPerformReboot),
-                    RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS);
-        } else if (mitigationCount >= 7) {
-            return Math.min(getMaxRescueLevel(mayPerformReboot), RESCUE_LEVEL_FACTORY_RESET);
-        } else {
-            return RESCUE_LEVEL_NONE;
-        }
-    }
-
-    /**
-     * Get the rescue level to perform if this is the n-th attempt at mitigating failure.
-     *
-     * @param mitigationCount the mitigation attempt number (1 = first attempt etc.).
-     * @return the rescue level for the n-th mitigation attempt.
-     */
-    private static @RescueLevels int getRescueLevel(int mitigationCount) {
-        if (mitigationCount == 1) {
-            return Level.reboot();
-        } else if (mitigationCount >= 2) {
-            return Math.min(getMaxRescueLevel(), Level.factoryReset());
-        } else {
-            return Level.none();
-        }
-    }
-
-    private static void executeRescueLevel(Context context, @Nullable String failedPackage,
-            int level) {
-        Slog.w(TAG, "Attempting rescue level " + levelToString(level));
-        try {
-            executeRescueLevelInternal(context, level, failedPackage);
-            EventLog.writeEvent(LOG_TAG_RESCUE_SUCCESS, level);
-            String successMsg = "Finished rescue level " + levelToString(level);
-            if (!TextUtils.isEmpty(failedPackage)) {
-                successMsg += " for package " + failedPackage;
-            }
-            logCrashRecoveryEvent(Log.DEBUG, successMsg);
-        } catch (Throwable t) {
-            logRescueException(level, failedPackage, t);
-        }
-    }
-
-    private static void executeRescueLevelInternal(Context context, int level, @Nullable
-            String failedPackage) throws Exception {
-        if (Flags.recoverabilityDetection()) {
-            executeRescueLevelInternalNew(context, level, failedPackage);
-        } else {
-            executeRescueLevelInternalOld(context, level, failedPackage);
-        }
-    }
-
-    private static void executeRescueLevelInternalOld(Context context, int level, @Nullable
-            String failedPackage) throws Exception {
-        CrashRecoveryStatsLog.write(CrashRecoveryStatsLog.RESCUE_PARTY_RESET_REPORTED,
-                level, levelToString(level));
-        // Try our best to reset all settings possible, and once finished
-        // rethrow any exception that we encountered
-        Exception res = null;
-        switch (level) {
-            case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
-                break;
-            case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
-                break;
-            case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
-                break;
-            case LEVEL_WARM_REBOOT:
-                executeWarmReboot(context, level, failedPackage);
-                break;
-            case LEVEL_FACTORY_RESET:
-                // Before the completion of Reboot, if any crash happens then PackageWatchdog
-                // escalates to next level i.e. factory reset, as they happen in separate threads.
-                // Adding a check to prevent factory reset to execute before above reboot completes.
-                // Note: this reboot property is not persistent resets after reboot is completed.
-                if (isRebootPropertySet()) {
-                    return;
-                }
-                executeFactoryReset(context, level, failedPackage);
-                break;
-        }
-
-        if (res != null) {
-            throw res;
-        }
-    }
-
-    private static void executeRescueLevelInternalNew(Context context, @RescueLevels int level,
-            @Nullable String failedPackage) throws Exception {
-        CrashRecoveryStatsLog.write(CrashRecoveryStatsLog.RESCUE_PARTY_RESET_REPORTED,
-                level, levelToString(level));
-        switch (level) {
-            case RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET:
-                break;
-            case RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET:
-                break;
-            case RESCUE_LEVEL_WARM_REBOOT:
-                executeWarmReboot(context, level, failedPackage);
-                break;
-            case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
-                // do nothing
-                break;
-            case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
-                // do nothing
-                break;
-            case RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
-                // do nothing
-                break;
-            case RESCUE_LEVEL_FACTORY_RESET:
-                // Before the completion of Reboot, if any crash happens then PackageWatchdog
-                // escalates to next level i.e. factory reset, as they happen in separate threads.
-                // Adding a check to prevent factory reset to execute before above reboot completes.
-                // Note: this reboot property is not persistent resets after reboot is completed.
-                if (isRebootPropertySet()) {
-                    return;
-                }
-                executeFactoryReset(context, level, failedPackage);
-                break;
-        }
-    }
-
-    private static void executeWarmReboot(Context context, int level,
-            @Nullable String failedPackage) {
-        if (Flags.deprecateFlagsAndSettingsResets()) {
-            if (shouldThrottleReboot()) {
-                return;
-            }
-        }
-
-        // Request the reboot from a separate thread to avoid deadlock on PackageWatchdog
-        // when device shutting down.
-        setRebootProperty(true);
-
-        if (Flags.synchronousRebootInRescueParty()) {
-            try {
-                PowerManager pm = context.getSystemService(PowerManager.class);
-                if (pm != null) {
-                    pm.reboot(TAG);
-                }
-            } catch (Throwable t) {
-                logRescueException(level, failedPackage, t);
-            }
-        } else {
-            Runnable runnable = () -> {
-                try {
-                    PowerManager pm = context.getSystemService(PowerManager.class);
-                    if (pm != null) {
-                        pm.reboot(TAG);
-                    }
-                } catch (Throwable t) {
-                    logRescueException(level, failedPackage, t);
-                }
-            };
-            Thread thread = new Thread(runnable);
-            thread.start();
-        }
-    }
-
-    private static void executeFactoryReset(Context context, int level,
-            @Nullable String failedPackage) {
-        if (Flags.deprecateFlagsAndSettingsResets()) {
-            if (shouldThrottleReboot()) {
-                return;
-            }
-        }
-        setFactoryResetProperty(true);
-        long now = System.currentTimeMillis();
-        setLastFactoryResetTimeMs(now);
-
-        if (Flags.synchronousRebootInRescueParty()) {
-            try {
-                RecoverySystem.rebootPromptAndWipeUserData(context, TAG + "," + failedPackage);
-            } catch (Throwable t) {
-                logRescueException(level, failedPackage, t);
-            }
-        } else {
-            Runnable runnable = new Runnable() {
-                @Override
-                public void run() {
-                    try {
-                        RecoverySystem.rebootPromptAndWipeUserData(context,
-                            TAG + "," + failedPackage);
-                    } catch (Throwable t) {
-                        logRescueException(level, failedPackage, t);
-                    }
-                }
-            };
-            Thread thread = new Thread(runnable);
-            thread.start();
-        }
-    }
-
-
-    private static String getCompleteMessage(Throwable t) {
-        final StringBuilder builder = new StringBuilder();
-        builder.append(t.getMessage());
-        while ((t = t.getCause()) != null) {
-            builder.append(": ").append(t.getMessage());
-        }
-        return builder.toString();
-    }
-
-    private static void logRescueException(int level, @Nullable String failedPackageName,
-            Throwable t) {
-        final String msg = getCompleteMessage(t);
-        EventLog.writeEvent(LOG_TAG_RESCUE_FAILURE, level, msg);
-        String failureMsg = "Failed rescue level " + levelToString(level);
-        if (!TextUtils.isEmpty(failedPackageName)) {
-            failureMsg += " for package " + failedPackageName;
-        }
-        logCrashRecoveryEvent(Log.ERROR, failureMsg + ": " + msg);
-    }
-
-    private static int mapRescueLevelToUserImpact(int rescueLevel) {
-        if (Flags.recoverabilityDetection()) {
-            switch (rescueLevel) {
-                case RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET:
-                    return PackageHealthObserverImpact.USER_IMPACT_LEVEL_10;
-                case RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET:
-                    return PackageHealthObserverImpact.USER_IMPACT_LEVEL_40;
-                case RESCUE_LEVEL_WARM_REBOOT:
-                    return PackageHealthObserverImpact.USER_IMPACT_LEVEL_50;
-                case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
-                    return PackageHealthObserverImpact.USER_IMPACT_LEVEL_71;
-                case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
-                    return PackageHealthObserverImpact.USER_IMPACT_LEVEL_75;
-                case RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
-                    return PackageHealthObserverImpact.USER_IMPACT_LEVEL_80;
-                case RESCUE_LEVEL_FACTORY_RESET:
-                    return PackageHealthObserverImpact.USER_IMPACT_LEVEL_100;
-                default:
-                    return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
-            }
-        } else {
-            switch (rescueLevel) {
-                case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
-                case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
-                    return PackageHealthObserverImpact.USER_IMPACT_LEVEL_10;
-                case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
-                case LEVEL_WARM_REBOOT:
-                    return PackageHealthObserverImpact.USER_IMPACT_LEVEL_50;
-                case LEVEL_FACTORY_RESET:
-                    return PackageHealthObserverImpact.USER_IMPACT_LEVEL_100;
-                default:
-                    return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
-            }
-        }
-    }
-
-    /**
-     * Handle mitigation action for package failures. This observer will be register to Package
-     * Watchdog and will receive calls about package failures. This observer is persistent so it
-     * may choose to mitigate failures for packages it has not explicitly asked to observe.
-     */
-    public static class RescuePartyObserver implements PackageHealthObserver {
-
-        private final Context mContext;
-        private final Map<String, Set<String>> mCallingPackageNamespaceSetMap = new HashMap<>();
-        private final Map<String, Set<String>> mNamespaceCallingPackageSetMap = new HashMap<>();
-
-        @GuardedBy("RescuePartyObserver.class")
-        static RescuePartyObserver sRescuePartyObserver;
-
-        private RescuePartyObserver(Context context) {
-            mContext = context;
-        }
-
-        /** Creates or gets singleton instance of RescueParty. */
-        public static RescuePartyObserver getInstance(Context context) {
-            synchronized (RescuePartyObserver.class) {
-                if (sRescuePartyObserver == null) {
-                    sRescuePartyObserver = new RescuePartyObserver(context);
-                }
-                return sRescuePartyObserver;
-            }
-        }
-
-        /** Gets singleton instance. It returns null if the instance is not created yet.*/
-        @Nullable
-        public static RescuePartyObserver getInstanceIfCreated() {
-            synchronized (RescuePartyObserver.class) {
-                return sRescuePartyObserver;
-            }
-        }
-
-        @VisibleForTesting
-        static void reset() {
-            synchronized (RescuePartyObserver.class) {
-                sRescuePartyObserver = null;
-            }
-        }
-
-        @Override
-        public int onHealthCheckFailed(@Nullable VersionedPackage failedPackage,
-                @FailureReasons int failureReason, int mitigationCount) {
-            int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
-            if (!isDisabled() && (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
-                    || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING)) {
-                if (Flags.recoverabilityDetection()) {
-                    if (!Flags.deprecateFlagsAndSettingsResets()) {
-                        impact =  mapRescueLevelToUserImpact(getRescueLevel(mitigationCount,
-                                mayPerformReboot(failedPackage), failedPackage));
-                    } else {
-                        impact =  mapRescueLevelToUserImpact(getRescueLevel(mitigationCount));
-                    }
-                } else {
-                    impact =  mapRescueLevelToUserImpact(getRescueLevel(mitigationCount,
-                            mayPerformReboot(failedPackage)));
-                }
-            }
-
-            Slog.i(TAG, "Checking available remediations for health check failure."
-                    + " failedPackage: "
-                    + (failedPackage == null ? null : failedPackage.getPackageName())
-                    + " failureReason: " + failureReason
-                    + " available impact: " + impact);
-            return impact;
-        }
-
-        @Override
-        public int onExecuteHealthCheckMitigation(@Nullable VersionedPackage failedPackage,
-                @FailureReasons int failureReason, int mitigationCount) {
-            if (isDisabled()) {
-                return MITIGATION_RESULT_SKIPPED;
-            }
-            Slog.i(TAG, "Executing remediation."
-                    + " failedPackage: "
-                    + (failedPackage == null ? null : failedPackage.getPackageName())
-                    + " failureReason: " + failureReason
-                    + " mitigationCount: " + mitigationCount);
-            if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
-                    || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) {
-                final int level;
-                if (Flags.recoverabilityDetection()) {
-                    if (!Flags.deprecateFlagsAndSettingsResets()) {
-                        level = getRescueLevel(mitigationCount, mayPerformReboot(failedPackage),
-                                failedPackage);
-                    } else {
-                        level = getRescueLevel(mitigationCount);
-                    }
-                } else {
-                    level = getRescueLevel(mitigationCount, mayPerformReboot(failedPackage));
-                }
-                executeRescueLevel(mContext,
-                        failedPackage == null ? null : failedPackage.getPackageName(), level);
-                return MITIGATION_RESULT_SUCCESS;
-            } else {
-                return MITIGATION_RESULT_SKIPPED;
-            }
-        }
-
-        @Override
-        public boolean isPersistent() {
-            return true;
-        }
-
-        @Override
-        public boolean mayObservePackage(String packageName) {
-            PackageManager pm = mContext.getPackageManager();
-            try {
-                // A package is a module if this is non-null
-                if (pm.getModuleInfo(packageName, 0) != null) {
-                    return true;
-                }
-            } catch (PackageManager.NameNotFoundException | IllegalStateException ignore) {
-            }
-
-            return isPersistentSystemApp(packageName);
-        }
-
-        @Override
-        public int onBootLoop(int mitigationCount) {
-            if (isDisabled()) {
-                return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
-            }
-            if (Flags.recoverabilityDetection()) {
-                if (!Flags.deprecateFlagsAndSettingsResets()) {
-                    return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount,
-                            true, /*failedPackage=*/ null));
-                } else {
-                    return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount));
-                }
-            } else {
-                return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount, true));
-            }
-        }
-
-        @Override
-        public int onExecuteBootLoopMitigation(int mitigationCount) {
-            if (isDisabled()) {
-                return MITIGATION_RESULT_SKIPPED;
-            }
-            boolean mayPerformReboot = !shouldThrottleReboot();
-            final int level;
-            if (Flags.recoverabilityDetection()) {
-                if (!Flags.deprecateFlagsAndSettingsResets()) {
-                    level = getRescueLevel(mitigationCount, mayPerformReboot,
-                            /*failedPackage=*/ null);
-                } else {
-                    level = getRescueLevel(mitigationCount);
-                }
-            } else {
-                level = getRescueLevel(mitigationCount, mayPerformReboot);
-            }
-            executeRescueLevel(mContext, /*failedPackage=*/ null, level);
-            return MITIGATION_RESULT_SUCCESS;
-        }
-
-        @Override
-        public String getUniqueIdentifier() {
-            return NAME;
-        }
-
-        /**
-         * Returns {@code true} if the failing package is non-null and performing a reboot or
-         * prompting a factory reset is an acceptable mitigation strategy for the package's
-         * failure, {@code false} otherwise.
-         */
-        private boolean mayPerformReboot(@Nullable VersionedPackage failingPackage) {
-            if (failingPackage == null) {
-                return false;
-            }
-            if (shouldThrottleReboot())  {
-                return false;
-            }
-
-            return isPersistentSystemApp(failingPackage.getPackageName());
-        }
-
-        private boolean isPersistentSystemApp(@NonNull String packageName) {
-            PackageManager pm = mContext.getPackageManager();
-            try {
-                ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
-                return (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK;
-            } catch (PackageManager.NameNotFoundException e) {
-                return false;
-            }
-        }
-
-        private synchronized void recordDeviceConfigAccess(@NonNull String callingPackage,
-                @NonNull String namespace) {
-            if (!Flags.deprecateFlagsAndSettingsResets()) {
-                // Record it in calling packages to namespace map
-                Set<String> namespaceSet = mCallingPackageNamespaceSetMap.get(callingPackage);
-                if (namespaceSet == null) {
-                    namespaceSet = new ArraySet<>();
-                    mCallingPackageNamespaceSetMap.put(callingPackage, namespaceSet);
-                }
-                namespaceSet.add(namespace);
-                // Record it in namespace to calling packages map
-                Set<String> callingPackageSet = mNamespaceCallingPackageSetMap.get(namespace);
-                if (callingPackageSet == null) {
-                    callingPackageSet = new ArraySet<>();
-                }
-                callingPackageSet.add(callingPackage);
-                mNamespaceCallingPackageSetMap.put(namespace, callingPackageSet);
-            }
-        }
-
-        private synchronized Set<String> getAffectedNamespaceSet(String failedPackage) {
-            return mCallingPackageNamespaceSetMap.get(failedPackage);
-        }
-
-        private synchronized Set<String> getAllAffectedNamespaceSet() {
-            return new HashSet<String>(mNamespaceCallingPackageSetMap.keySet());
-        }
-
-        private synchronized Set<String> getCallingPackagesSet(String namespace) {
-            return mNamespaceCallingPackageSetMap.get(namespace);
-        }
-    }
-
-    /**
-     * Returns {@code true} if Rescue Party is allowed to attempt a reboot or factory reset.
-     * Will return {@code false} if a factory reset was already offered recently.
-     */
-    private static boolean shouldThrottleReboot() {
-        Long lastResetTime = getLastFactoryResetTimeMs();
-        long now = System.currentTimeMillis();
-        long throttleDurationMin = SystemProperties.getLong(PROP_THROTTLE_DURATION_MIN_FLAG,
-                DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN);
-        return now < lastResetTime + TimeUnit.MINUTES.toMillis(throttleDurationMin);
-    }
-
-    private static int[] getAllUserIds() {
-        int systemUserId = UserHandle.SYSTEM.getIdentifier();
-        int[] userIds = { systemUserId };
-        try {
-            for (File file : FileUtils.listFilesOrEmpty(
-                    Environment.getDataSystemDeviceProtectedDirectory())) {
-                try {
-                    final int userId = Integer.parseInt(file.getName());
-                    if (userId != systemUserId) {
-                        userIds = ArrayUtils.appendInt(userIds, userId);
-                    }
-                } catch (NumberFormatException ignored) {
-                }
-            }
-        } catch (Throwable t) {
-            Slog.w(TAG, "Trouble discovering users", t);
-        }
-        return userIds;
-    }
-
-    /**
-     * Hacky test to check if the device has an active USB connection, which is
-     * a good proxy for someone doing local development work.
-     */
-    private static boolean isUsbActive() {
-        if (SystemProperties.getBoolean(PROP_VIRTUAL_DEVICE, false)) {
-            Slog.v(TAG, "Assuming virtual device is connected over USB");
-            return true;
-        }
-        try {
-            final String state = FileUtils
-                    .readTextFile(new File("/sys/class/android_usb/android0/state"), 128, "");
-            return "CONFIGURED".equals(state.trim());
-        } catch (Throwable t) {
-            Slog.w(TAG, "Failed to determine if device was on USB", t);
-            return false;
-        }
-    }
-
-    private static class Level {
-        static int none() {
-            return Flags.recoverabilityDetection() ? RESCUE_LEVEL_NONE : LEVEL_NONE;
-        }
-
-        static int reboot() {
-            return Flags.recoverabilityDetection() ? RESCUE_LEVEL_WARM_REBOOT : LEVEL_WARM_REBOOT;
-        }
-
-        static int factoryReset() {
-            return Flags.recoverabilityDetection()
-                    ? RESCUE_LEVEL_FACTORY_RESET
-                    : LEVEL_FACTORY_RESET;
-        }
-    }
-
-    private static String levelToString(int level) {
-        if (Flags.recoverabilityDetection()) {
-            switch (level) {
-                case RESCUE_LEVEL_NONE:
-                    return "NONE";
-                case RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET:
-                    return "SCOPED_DEVICE_CONFIG_RESET";
-                case RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET:
-                    return "ALL_DEVICE_CONFIG_RESET";
-                case RESCUE_LEVEL_WARM_REBOOT:
-                    return "WARM_REBOOT";
-                case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
-                    return "RESET_SETTINGS_UNTRUSTED_DEFAULTS";
-                case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
-                    return "RESET_SETTINGS_UNTRUSTED_CHANGES";
-                case RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
-                    return "RESET_SETTINGS_TRUSTED_DEFAULTS";
-                case RESCUE_LEVEL_FACTORY_RESET:
-                    return "FACTORY_RESET";
-                default:
-                    return Integer.toString(level);
-            }
-        } else {
-            switch (level) {
-                case LEVEL_NONE:
-                    return "NONE";
-                case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
-                    return "RESET_SETTINGS_UNTRUSTED_DEFAULTS";
-                case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
-                    return "RESET_SETTINGS_UNTRUSTED_CHANGES";
-                case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
-                    return "RESET_SETTINGS_TRUSTED_DEFAULTS";
-                case LEVEL_WARM_REBOOT:
-                    return "WARM_REBOOT";
-                case LEVEL_FACTORY_RESET:
-                    return "FACTORY_RESET";
-                default:
-                    return Integer.toString(level);
-            }
-        }
-    }
-}
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/crashrecovery/CrashRecoveryModule.java b/packages/CrashRecovery/services/module/java/com/android/server/crashrecovery/CrashRecoveryModule.java
deleted file mode 100644
index 8a81aaa..0000000
--- a/packages/CrashRecovery/services/module/java/com/android/server/crashrecovery/CrashRecoveryModule.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.crashrecovery;
-
-import android.content.Context;
-
-import com.android.server.PackageWatchdog;
-import com.android.server.RescueParty;
-import com.android.server.SystemService;
-
-
-/** This class encapsulate the lifecycle methods of CrashRecovery module.
- *
- * @hide
- */
-public class CrashRecoveryModule {
-    private static final String TAG = "CrashRecoveryModule";
-
-    /** Lifecycle definition for CrashRecovery module. */
-    public static class Lifecycle extends SystemService {
-        private Context mSystemContext;
-        private PackageWatchdog mPackageWatchdog;
-
-        public Lifecycle(Context context) {
-            super(context);
-            mSystemContext = context;
-            mPackageWatchdog = PackageWatchdog.getInstance(context);
-        }
-
-        @Override
-        public void onStart() {
-            RescueParty.registerHealthObserver(mSystemContext);
-            mPackageWatchdog.registerShutdownBroadcastReceiver();
-            mPackageWatchdog.noteBoot();
-        }
-
-        @Override
-        public void onBootPhase(int phase) {
-            if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
-                mPackageWatchdog.onPackagesReady();
-            }
-        }
-    }
-}
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/crashrecovery/CrashRecoveryUtils.java b/packages/CrashRecovery/services/module/java/com/android/server/crashrecovery/CrashRecoveryUtils.java
deleted file mode 100644
index 2e2a937..0000000
--- a/packages/CrashRecovery/services/module/java/com/android/server/crashrecovery/CrashRecoveryUtils.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.crashrecovery;
-
-import android.os.Environment;
-import android.util.IndentingPrintWriter;
-import android.util.Log;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.FileReader;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.time.LocalDateTime;
-import java.time.ZoneId;
-
-/**
- * Class containing helper methods for the CrashRecoveryModule.
- *
- * @hide
- */
-public class CrashRecoveryUtils {
-    private static final String TAG = "CrashRecoveryUtils";
-    private static final long MAX_CRITICAL_INFO_DUMP_SIZE = 1000 * 1000; // ~1MB
-    private static final Object sFileLock = new Object();
-
-    /** Persist recovery related events in crashrecovery events file.**/
-    public static void logCrashRecoveryEvent(int priority, String msg) {
-        Log.println(priority, TAG, msg);
-        try {
-            File fname = getCrashRecoveryEventsFile();
-            synchronized (sFileLock) {
-                FileOutputStream out = new FileOutputStream(fname, true);
-                PrintWriter pw = new PrintWriter(out);
-                String dateString = LocalDateTime.now(ZoneId.systemDefault()).toString();
-                pw.println(dateString + ": " + msg);
-                pw.close();
-            }
-        } catch (IOException e) {
-            Log.e(TAG, "Unable to log CrashRecoveryEvents " + e.getMessage());
-        }
-    }
-
-    /** Dump recovery related events from crashrecovery events file.**/
-    public static void dumpCrashRecoveryEvents(IndentingPrintWriter pw) {
-        pw.println("CrashRecovery Events: ");
-        pw.increaseIndent();
-        final File file = getCrashRecoveryEventsFile();
-        final long skipSize = file.length() - MAX_CRITICAL_INFO_DUMP_SIZE;
-        synchronized (sFileLock) {
-            try (BufferedReader in = new BufferedReader(new FileReader(file))) {
-                if (skipSize > 0) {
-                    in.skip(skipSize);
-                }
-                String line;
-                while ((line = in.readLine()) != null) {
-                    pw.println(line);
-                }
-            } catch (IOException e) {
-                Log.e(TAG, "Unable to dump CrashRecoveryEvents " + e.getMessage());
-            }
-        }
-        pw.decreaseIndent();
-    }
-
-    private static File getCrashRecoveryEventsFile() {
-        File systemDir = new File(Environment.getDataDirectory(), "system");
-        return new File(systemDir, "crashrecovery-events.txt");
-    }
-}
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java
deleted file mode 100644
index 4978df4..0000000
--- a/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ /dev/null
@@ -1,785 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.rollback;
-
-import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SKIPPED;
-import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SUCCESS;
-import static com.android.server.crashrecovery.CrashRecoveryUtils.logCrashRecoveryEvent;
-
-import android.annotation.AnyThread;
-import android.annotation.FlaggedApi;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SuppressLint;
-import android.annotation.SystemApi;
-import android.annotation.WorkerThread;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.VersionedPackage;
-import android.content.rollback.PackageRollbackInfo;
-import android.content.rollback.RollbackInfo;
-import android.content.rollback.RollbackManager;
-import android.crashrecovery.flags.Flags;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.PowerManager;
-import android.os.SystemProperties;
-import android.sysprop.CrashRecoveryProperties;
-import android.util.ArraySet;
-import android.util.FileUtils;
-import android.util.Log;
-import android.util.Slog;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
-import com.android.server.PackageWatchdog;
-import com.android.server.PackageWatchdog.FailureReasons;
-import com.android.server.PackageWatchdog.PackageHealthObserver;
-import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
-import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.FileReader;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Set;
-import java.util.function.Consumer;
-
-/**
- * {@link PackageHealthObserver} for {@link RollbackManagerService}.
- * This class monitors crashes and triggers RollbackManager rollback accordingly.
- * It also monitors native crashes for some short while after boot.
- *
- * @hide
- */
-@FlaggedApi(Flags.FLAG_ENABLE_CRASHRECOVERY)
-@SuppressLint({"CallbackName"})
-@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
-public final class RollbackPackageHealthObserver implements PackageHealthObserver {
-    private static final String TAG = "RollbackPackageHealthObserver";
-    private static final String NAME = "rollback-observer";
-    private static final String CLASS_NAME = RollbackPackageHealthObserver.class.getName();
-
-    private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
-            | ApplicationInfo.FLAG_SYSTEM;
-
-    private static final String PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG =
-            "persist.device_config.configuration.disable_high_impact_rollback";
-
-    private final Context mContext;
-    private final Handler mHandler;
-    private final File mLastStagedRollbackIdsFile;
-    private final File mTwoPhaseRollbackEnabledFile;
-    // Staged rollback ids that have been committed but their session is not yet ready
-    private final Set<Integer> mPendingStagedRollbackIds = new ArraySet<>();
-    // True if needing to roll back only rebootless apexes when native crash happens
-    private boolean mTwoPhaseRollbackEnabled;
-
-    @VisibleForTesting
-    public RollbackPackageHealthObserver(@NonNull Context context) {
-        mContext = context;
-        HandlerThread handlerThread = new HandlerThread("RollbackPackageHealthObserver");
-        handlerThread.start();
-        mHandler = new Handler(handlerThread.getLooper());
-        File dataDir = new File(Environment.getDataDirectory(), "rollback-observer");
-        dataDir.mkdirs();
-        mLastStagedRollbackIdsFile = new File(dataDir, "last-staged-rollback-ids");
-        mTwoPhaseRollbackEnabledFile = new File(dataDir, "two-phase-rollback-enabled");
-        PackageWatchdog.getInstance(mContext).registerHealthObserver(context.getMainExecutor(),
-                this);
-
-        if (SystemProperties.getBoolean("sys.boot_completed", false)) {
-            // Load the value from the file if system server has crashed and restarted
-            mTwoPhaseRollbackEnabled = readBoolean(mTwoPhaseRollbackEnabledFile);
-        } else {
-            // Disable two-phase rollback for a normal reboot. We assume the rebootless apex
-            // installed before reboot is stable if native crash didn't happen.
-            mTwoPhaseRollbackEnabled = false;
-            writeBoolean(mTwoPhaseRollbackEnabledFile, false);
-        }
-    }
-
-    @Override
-    public int onHealthCheckFailed(@Nullable VersionedPackage failedPackage,
-            @FailureReasons int failureReason, int mitigationCount) {
-        int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
-        if (Flags.recoverabilityDetection()) {
-            List<RollbackInfo> availableRollbacks = getAvailableRollbacks();
-            List<RollbackInfo> lowImpactRollbacks = getRollbacksAvailableForImpactLevel(
-                    availableRollbacks, PackageManager.ROLLBACK_USER_IMPACT_LOW);
-            if (!lowImpactRollbacks.isEmpty()) {
-                if (failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
-                    // For native crashes, we will directly roll back any available rollbacks at low
-                    // impact level
-                    impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
-                } else if (getRollbackForPackage(failedPackage, lowImpactRollbacks) != null) {
-                    // Rollback is available for crashing low impact package
-                    impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
-                } else {
-                    impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70;
-                }
-            }
-        } else {
-            boolean anyRollbackAvailable = !mContext.getSystemService(RollbackManager.class)
-                    .getAvailableRollbacks().isEmpty();
-
-            if (failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH
-                    && anyRollbackAvailable) {
-                // For native crashes, we will directly roll back any available rollbacks
-                // Note: For non-native crashes the rollback-all step has higher impact
-                impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
-            } else if (getAvailableRollback(failedPackage) != null) {
-                // Rollback is available, we may get a callback into #onExecuteHealthCheckMitigation
-                impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
-            } else if (anyRollbackAvailable) {
-                // If any rollbacks are available, we will commit them
-                impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70;
-            }
-        }
-
-        Slog.i(TAG, "Checking available remediations for health check failure."
-                + " failedPackage: "
-                + (failedPackage == null ? null : failedPackage.getPackageName())
-                + " failureReason: " + failureReason
-                + " available impact: " + impact);
-        return impact;
-    }
-
-    @Override
-    public int onExecuteHealthCheckMitigation(@Nullable VersionedPackage failedPackage,
-            @FailureReasons int rollbackReason, int mitigationCount) {
-        Slog.i(TAG, "Executing remediation."
-                + " failedPackage: "
-                + (failedPackage == null ? null : failedPackage.getPackageName())
-                + " rollbackReason: " + rollbackReason
-                + " mitigationCount: " + mitigationCount);
-        if (Flags.recoverabilityDetection()) {
-            List<RollbackInfo> availableRollbacks = getAvailableRollbacks();
-            if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
-                mHandler.post(() -> rollbackAllLowImpact(availableRollbacks, rollbackReason));
-                return MITIGATION_RESULT_SUCCESS;
-            }
-
-            List<RollbackInfo> lowImpactRollbacks = getRollbacksAvailableForImpactLevel(
-                    availableRollbacks, PackageManager.ROLLBACK_USER_IMPACT_LOW);
-            RollbackInfo rollback = getRollbackForPackage(failedPackage, lowImpactRollbacks);
-            if (rollback != null) {
-                mHandler.post(() -> rollbackPackage(rollback, failedPackage, rollbackReason));
-            } else if (!lowImpactRollbacks.isEmpty()) {
-                // Apply all available low impact rollbacks.
-                mHandler.post(() -> rollbackAllLowImpact(availableRollbacks, rollbackReason));
-            }
-        } else {
-            if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
-                mHandler.post(() -> rollbackAll(rollbackReason));
-                return MITIGATION_RESULT_SUCCESS;
-            }
-
-            RollbackInfo rollback = getAvailableRollback(failedPackage);
-            if (rollback != null) {
-                mHandler.post(() -> rollbackPackage(rollback, failedPackage, rollbackReason));
-            } else {
-                mHandler.post(() -> rollbackAll(rollbackReason));
-            }
-        }
-
-        // Assume rollbacks executed successfully
-        return MITIGATION_RESULT_SUCCESS;
-    }
-
-    @Override
-    public int onBootLoop(int mitigationCount) {
-        int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
-        if (Flags.recoverabilityDetection()) {
-            List<RollbackInfo> availableRollbacks = getAvailableRollbacks();
-            if (!availableRollbacks.isEmpty()) {
-                impact = getUserImpactBasedOnRollbackImpactLevel(availableRollbacks);
-            }
-        }
-        return impact;
-    }
-
-    @Override
-    public int onExecuteBootLoopMitigation(int mitigationCount) {
-        if (Flags.recoverabilityDetection()) {
-            List<RollbackInfo> availableRollbacks = getAvailableRollbacks();
-
-            triggerLeastImpactLevelRollback(availableRollbacks,
-                    PackageWatchdog.FAILURE_REASON_BOOT_LOOP);
-            return MITIGATION_RESULT_SUCCESS;
-        }
-        return MITIGATION_RESULT_SKIPPED;
-    }
-
-    @Override
-    @NonNull
-    public String getUniqueIdentifier() {
-        return NAME;
-    }
-
-    @Override
-    public boolean isPersistent() {
-        return true;
-    }
-
-    @Override
-    public boolean mayObservePackage(@NonNull String packageName) {
-        if (getAvailableRollbacks().isEmpty()) {
-            return false;
-        }
-        return isPersistentSystemApp(packageName);
-    }
-
-    private List<RollbackInfo> getAvailableRollbacks() {
-        return mContext.getSystemService(RollbackManager.class).getAvailableRollbacks();
-    }
-
-    private boolean isPersistentSystemApp(@NonNull String packageName) {
-        PackageManager pm = mContext.getPackageManager();
-        try {
-            ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
-            return (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK;
-        } catch (PackageManager.NameNotFoundException e) {
-            return false;
-        }
-    }
-
-    private void assertInWorkerThread() {
-        Preconditions.checkState(mHandler.getLooper().isCurrentThread());
-    }
-
-    @AnyThread
-    @NonNull
-    public void notifyRollbackAvailable(@NonNull RollbackInfo rollback) {
-        mHandler.post(() -> {
-            // Enable two-phase rollback when a rebootless apex rollback is made available.
-            // We assume the rebootless apex is stable and is less likely to be the cause
-            // if native crash doesn't happen before reboot. So we will clear the flag and disable
-            // two-phase rollback after reboot.
-            if (isRebootlessApex(rollback)) {
-                mTwoPhaseRollbackEnabled = true;
-                writeBoolean(mTwoPhaseRollbackEnabledFile, true);
-            }
-        });
-    }
-
-    private static boolean isRebootlessApex(RollbackInfo rollback) {
-        if (!rollback.isStaged()) {
-            for (PackageRollbackInfo info : rollback.getPackages()) {
-                if (info.isApex()) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    /** Verifies the rollback state after a reboot and schedules polling for sometime after reboot
-     * to check for native crashes and mitigate them if needed.
-     */
-    @AnyThread
-    public void onBootCompletedAsync() {
-        mHandler.post(()->onBootCompleted());
-    }
-
-    @WorkerThread
-    private void onBootCompleted() {
-        assertInWorkerThread();
-
-        RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
-        if (!rollbackManager.getAvailableRollbacks().isEmpty()) {
-            // TODO(gavincorkery): Call into Package Watchdog from outside the observer
-            PackageWatchdog.getInstance(mContext).scheduleCheckAndMitigateNativeCrashes();
-        }
-
-        SparseArray<String> rollbackIds = popLastStagedRollbackIds();
-        for (int i = 0; i < rollbackIds.size(); i++) {
-            WatchdogRollbackLogger.logRollbackStatusOnBoot(mContext,
-                    rollbackIds.keyAt(i), rollbackIds.valueAt(i),
-                    rollbackManager.getRecentlyCommittedRollbacks());
-        }
-    }
-
-    @AnyThread
-    private RollbackInfo getAvailableRollback(VersionedPackage failedPackage) {
-        RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
-        for (RollbackInfo rollback : rollbackManager.getAvailableRollbacks()) {
-            for (PackageRollbackInfo packageRollback : rollback.getPackages()) {
-                if (packageRollback.getVersionRolledBackFrom().equals(failedPackage)) {
-                    return rollback;
-                }
-                // TODO(b/147666157): Extract version number of apk-in-apex so that we don't have
-                //  to rely on complicated reasoning as below
-
-                // Due to b/147666157, for apk in apex, we do not know the version we are rolling
-                // back from. But if a package X is embedded in apex A exclusively (not embedded in
-                // any other apex), which is not guaranteed, then it is sufficient to check only
-                // package names here, as the version of failedPackage and the PackageRollbackInfo
-                // can't be different. If failedPackage has a higher version, then it must have
-                // been updated somehow. There are two ways: it was updated by an update of apex A
-                // or updated directly as apk. In both cases, this rollback would have gotten
-                // expired when onPackageReplaced() was called. Since the rollback exists, it has
-                // same version as failedPackage.
-                if (packageRollback.isApkInApex()
-                        && packageRollback.getVersionRolledBackFrom().getPackageName()
-                        .equals(failedPackage.getPackageName())) {
-                    return rollback;
-                }
-            }
-        }
-        return null;
-    }
-
-    @AnyThread
-    private RollbackInfo getRollbackForPackage(@Nullable VersionedPackage failedPackage,
-            List<RollbackInfo> availableRollbacks) {
-        if (failedPackage == null) {
-            return null;
-        }
-
-        for (RollbackInfo rollback : availableRollbacks) {
-            for (PackageRollbackInfo packageRollback : rollback.getPackages()) {
-                if (packageRollback.getVersionRolledBackFrom().equals(failedPackage)) {
-                    return rollback;
-                }
-                // TODO(b/147666157): Extract version number of apk-in-apex so that we don't have
-                //  to rely on complicated reasoning as below
-
-                // Due to b/147666157, for apk in apex, we do not know the version we are rolling
-                // back from. But if a package X is embedded in apex A exclusively (not embedded in
-                // any other apex), which is not guaranteed, then it is sufficient to check only
-                // package names here, as the version of failedPackage and the PackageRollbackInfo
-                // can't be different. If failedPackage has a higher version, then it must have
-                // been updated somehow. There are two ways: it was updated by an update of apex A
-                // or updated directly as apk. In both cases, this rollback would have gotten
-                // expired when onPackageReplaced() was called. Since the rollback exists, it has
-                // same version as failedPackage.
-                if (packageRollback.isApkInApex()
-                        && packageRollback.getVersionRolledBackFrom().getPackageName()
-                        .equals(failedPackage.getPackageName())) {
-                    return rollback;
-                }
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Returns {@code true} if staged session associated with {@code rollbackId} was marked
-     * as handled, {@code false} if already handled.
-     */
-    @WorkerThread
-    private boolean markStagedSessionHandled(int rollbackId) {
-        assertInWorkerThread();
-        return mPendingStagedRollbackIds.remove(rollbackId);
-    }
-
-    /**
-     * Returns {@code true} if all pending staged rollback sessions were marked as handled,
-     * {@code false} if there is any left.
-     */
-    @WorkerThread
-    private boolean isPendingStagedSessionsEmpty() {
-        assertInWorkerThread();
-        return mPendingStagedRollbackIds.isEmpty();
-    }
-
-    private static boolean readBoolean(File file) {
-        try (FileInputStream fis = new FileInputStream(file)) {
-            return fis.read() == 1;
-        } catch (IOException ignore) {
-            return false;
-        }
-    }
-
-    private static void writeBoolean(File file, boolean value) {
-        try (FileOutputStream fos = new FileOutputStream(file)) {
-            fos.write(value ? 1 : 0);
-            fos.flush();
-            FileUtils.sync(fos);
-        } catch (IOException ignore) {
-        }
-    }
-
-    @WorkerThread
-    private void saveStagedRollbackId(int stagedRollbackId, @Nullable VersionedPackage logPackage) {
-        assertInWorkerThread();
-        writeStagedRollbackId(mLastStagedRollbackIdsFile, stagedRollbackId, logPackage);
-    }
-
-    static void writeStagedRollbackId(File file, int stagedRollbackId,
-            @Nullable VersionedPackage logPackage) {
-        try {
-            FileOutputStream fos = new FileOutputStream(file, true);
-            PrintWriter pw = new PrintWriter(fos);
-            String logPackageName = logPackage != null ? logPackage.getPackageName() : "";
-            pw.append(String.valueOf(stagedRollbackId)).append(",").append(logPackageName);
-            pw.println();
-            pw.flush();
-            FileUtils.sync(fos);
-            pw.close();
-        } catch (IOException e) {
-            Slog.e(TAG, "Failed to save last staged rollback id", e);
-            file.delete();
-        }
-    }
-
-    @WorkerThread
-    private SparseArray<String> popLastStagedRollbackIds() {
-        assertInWorkerThread();
-        try {
-            return readStagedRollbackIds(mLastStagedRollbackIdsFile);
-        } finally {
-            mLastStagedRollbackIdsFile.delete();
-        }
-    }
-
-    static SparseArray<String> readStagedRollbackIds(File file) {
-        SparseArray<String> result = new SparseArray<>();
-        try {
-            String line;
-            BufferedReader reader = new BufferedReader(new FileReader(file));
-            while ((line = reader.readLine()) != null) {
-                // Each line is of the format: "id,logging_package"
-                String[] values = line.trim().split(",");
-                String rollbackId = values[0];
-                String logPackageName = "";
-                if (values.length > 1) {
-                    logPackageName = values[1];
-                }
-                result.put(Integer.parseInt(rollbackId), logPackageName);
-            }
-        } catch (Exception ignore) {
-            return new SparseArray<>();
-        }
-        return result;
-    }
-
-
-    /**
-     * Returns true if the package name is the name of a module.
-     */
-    @AnyThread
-    private boolean isModule(String packageName) {
-        // Check if the package is listed among the system modules or is an
-        // APK inside an updatable APEX.
-        try {
-            PackageManager pm = mContext.getPackageManager();
-            final PackageInfo pkg = pm.getPackageInfo(packageName, 0 /* flags */);
-            String apexPackageName = pkg.getApexPackageName();
-            if (apexPackageName != null) {
-                packageName = apexPackageName;
-            }
-
-            return pm.getModuleInfo(packageName, 0 /* flags */) != null;
-        } catch (PackageManager.NameNotFoundException e) {
-            return false;
-        }
-    }
-
-    /**
-     * Rolls back the session that owns {@code failedPackage}
-     *
-     * @param rollback {@code rollbackInfo} of the {@code failedPackage}
-     * @param failedPackage the package that needs to be rolled back
-     */
-    @WorkerThread
-    private void rollbackPackage(RollbackInfo rollback, VersionedPackage failedPackage,
-            @FailureReasons int rollbackReason) {
-        assertInWorkerThread();
-        String failedPackageName = (failedPackage == null ? null : failedPackage.getPackageName());
-
-        Slog.i(TAG, "Rolling back package. RollbackId: " + rollback.getRollbackId()
-                + " failedPackage: " + failedPackageName
-                + " rollbackReason: " + rollbackReason);
-        logCrashRecoveryEvent(Log.DEBUG, String.format("Rolling back %s. Reason: %s",
-                failedPackageName, rollbackReason));
-        final RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
-        int reasonToLog = WatchdogRollbackLogger.mapFailureReasonToMetric(rollbackReason);
-        final String failedPackageToLog;
-        if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
-            failedPackageToLog = SystemProperties.get(
-                    "sys.init.updatable_crashing_process_name", "");
-        } else {
-            failedPackageToLog = failedPackage.getPackageName();
-        }
-        VersionedPackage logPackageTemp = null;
-        if (isModule(failedPackage.getPackageName())) {
-            logPackageTemp = WatchdogRollbackLogger.getLogPackage(mContext, failedPackage);
-        }
-
-        final VersionedPackage logPackage = logPackageTemp;
-        WatchdogRollbackLogger.logEvent(logPackage,
-                CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE,
-                reasonToLog, failedPackageToLog);
-
-        Consumer<Intent> onResult = result -> {
-            assertInWorkerThread();
-            int status = result.getIntExtra(RollbackManager.EXTRA_STATUS,
-                    RollbackManager.STATUS_FAILURE);
-            if (status == RollbackManager.STATUS_SUCCESS) {
-                if (rollback.isStaged()) {
-                    int rollbackId = rollback.getRollbackId();
-                    saveStagedRollbackId(rollbackId, logPackage);
-                    WatchdogRollbackLogger.logEvent(logPackage,
-                            CrashRecoveryStatsLog
-                            .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED,
-                            reasonToLog, failedPackageToLog);
-
-                } else {
-                    WatchdogRollbackLogger.logEvent(logPackage,
-                            CrashRecoveryStatsLog
-                                    .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS,
-                            reasonToLog, failedPackageToLog);
-                }
-            } else {
-                WatchdogRollbackLogger.logEvent(logPackage,
-                        CrashRecoveryStatsLog
-                                .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE,
-                        reasonToLog, failedPackageToLog);
-            }
-            if (rollback.isStaged()) {
-                markStagedSessionHandled(rollback.getRollbackId());
-                // Wait for all pending staged sessions to get handled before rebooting.
-                if (isPendingStagedSessionsEmpty()) {
-                    CrashRecoveryProperties.attemptingReboot(true);
-                    mContext.getSystemService(PowerManager.class).reboot("Rollback staged install");
-                }
-            }
-        };
-
-        // Define a BroadcastReceiver to handle the result
-        BroadcastReceiver rollbackReceiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent result) {
-                mHandler.post(() -> onResult.accept(result));
-            }
-        };
-
-        String intentActionName = CLASS_NAME + rollback.getRollbackId();
-        // Register the BroadcastReceiver
-        mContext.registerReceiver(rollbackReceiver,
-                new IntentFilter(intentActionName),
-                Context.RECEIVER_NOT_EXPORTED);
-
-        Intent intentReceiver = new Intent(intentActionName);
-        intentReceiver.putExtra("rollbackId", rollback.getRollbackId());
-        intentReceiver.setPackage(mContext.getPackageName());
-        intentReceiver.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-
-        PendingIntent rollbackPendingIntent = PendingIntent.getBroadcast(mContext,
-                rollback.getRollbackId(),
-                intentReceiver,
-                PendingIntent.FLAG_MUTABLE);
-
-        rollbackManager.commitRollback(rollback.getRollbackId(),
-                Collections.singletonList(failedPackage),
-                rollbackPendingIntent.getIntentSender());
-    }
-
-    /**
-     * Two-phase rollback:
-     * 1. roll back rebootless apexes first
-     * 2. roll back all remaining rollbacks if native crash doesn't stop after (1) is done
-     *
-     * This approach gives us a better chance to correctly attribute native crash to rebootless
-     * apex update without rolling back Mainline updates which might contains critical security
-     * fixes.
-     */
-    @WorkerThread
-    private boolean useTwoPhaseRollback(List<RollbackInfo> rollbacks) {
-        assertInWorkerThread();
-        if (!mTwoPhaseRollbackEnabled) {
-            return false;
-        }
-
-        Slog.i(TAG, "Rolling back all rebootless APEX rollbacks");
-        boolean found = false;
-        for (RollbackInfo rollback : rollbacks) {
-            if (isRebootlessApex(rollback)) {
-                VersionedPackage firstRollback =
-                        rollback.getPackages().get(0).getVersionRolledBackFrom();
-                rollbackPackage(rollback, firstRollback,
-                        PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
-                found = true;
-            }
-        }
-        return found;
-    }
-
-    /**
-     * Rollback the package that has minimum rollback impact level.
-     * @param availableRollbacks all available rollbacks
-     * @param rollbackReason reason to rollback
-     */
-    private void triggerLeastImpactLevelRollback(List<RollbackInfo> availableRollbacks,
-            @FailureReasons int rollbackReason) {
-        int minRollbackImpactLevel = getMinRollbackImpactLevel(availableRollbacks);
-
-        if (minRollbackImpactLevel == PackageManager.ROLLBACK_USER_IMPACT_LOW) {
-            // Apply all available low impact rollbacks.
-            mHandler.post(() -> rollbackAllLowImpact(availableRollbacks, rollbackReason));
-        } else if (minRollbackImpactLevel == PackageManager.ROLLBACK_USER_IMPACT_HIGH) {
-            // Check disable_high_impact_rollback device config before performing rollback
-            if (SystemProperties.getBoolean(PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG, false)) {
-                return;
-            }
-            // Rollback one package at a time. If that doesn't resolve the issue, rollback
-            // next with same impact level.
-            mHandler.post(() -> rollbackHighImpact(availableRollbacks, rollbackReason));
-        }
-    }
-
-    /**
-     * sort the available high impact rollbacks by first package name to have a deterministic order.
-     * Apply the first available rollback.
-     * @param availableRollbacks all available rollbacks
-     * @param rollbackReason reason to rollback
-     */
-    @WorkerThread
-    private void rollbackHighImpact(List<RollbackInfo> availableRollbacks,
-            @FailureReasons int rollbackReason) {
-        assertInWorkerThread();
-        List<RollbackInfo> highImpactRollbacks =
-                getRollbacksAvailableForImpactLevel(
-                        availableRollbacks, PackageManager.ROLLBACK_USER_IMPACT_HIGH);
-
-        // sort rollbacks based on package name of the first package. This is to have a
-        // deterministic order of rollbacks.
-        List<RollbackInfo> sortedHighImpactRollbacks = highImpactRollbacks.stream().sorted(
-                Comparator.comparing(a -> a.getPackages().get(0).getPackageName())).toList();
-        VersionedPackage firstRollback =
-                sortedHighImpactRollbacks
-                        .get(0)
-                        .getPackages()
-                        .get(0)
-                        .getVersionRolledBackFrom();
-        Slog.i(TAG, "Rolling back high impact rollback for package: "
-                + firstRollback.getPackageName());
-        rollbackPackage(sortedHighImpactRollbacks.get(0), firstRollback, rollbackReason);
-    }
-
-    @WorkerThread
-    private void rollbackAll(@FailureReasons int rollbackReason) {
-        assertInWorkerThread();
-        RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
-        List<RollbackInfo> rollbacks = rollbackManager.getAvailableRollbacks();
-        if (useTwoPhaseRollback(rollbacks)) {
-            return;
-        }
-
-        Slog.i(TAG, "Rolling back all available rollbacks");
-        // Add all rollback ids to mPendingStagedRollbackIds, so that we do not reboot before all
-        // pending staged rollbacks are handled.
-        for (RollbackInfo rollback : rollbacks) {
-            if (rollback.isStaged()) {
-                mPendingStagedRollbackIds.add(rollback.getRollbackId());
-            }
-        }
-
-        for (RollbackInfo rollback : rollbacks) {
-            VersionedPackage firstRollback =
-                    rollback.getPackages().get(0).getVersionRolledBackFrom();
-            rollbackPackage(rollback, firstRollback, rollbackReason);
-        }
-    }
-
-    /**
-     * Rollback all available low impact rollbacks
-     * @param availableRollbacks all available rollbacks
-     * @param rollbackReason reason to rollbacks
-     */
-    @WorkerThread
-    private void rollbackAllLowImpact(
-            List<RollbackInfo> availableRollbacks, @FailureReasons int rollbackReason) {
-        assertInWorkerThread();
-
-        List<RollbackInfo> lowImpactRollbacks = getRollbacksAvailableForImpactLevel(
-                availableRollbacks,
-                PackageManager.ROLLBACK_USER_IMPACT_LOW);
-        if (useTwoPhaseRollback(lowImpactRollbacks)) {
-            return;
-        }
-
-        Slog.i(TAG, "Rolling back all available low impact rollbacks");
-        logCrashRecoveryEvent(Log.DEBUG, "Rolling back all available. Reason: " + rollbackReason);
-        // Add all rollback ids to mPendingStagedRollbackIds, so that we do not reboot before all
-        // pending staged rollbacks are handled.
-        for (RollbackInfo rollback : lowImpactRollbacks) {
-            if (rollback.isStaged()) {
-                mPendingStagedRollbackIds.add(rollback.getRollbackId());
-            }
-        }
-
-        for (RollbackInfo rollback : lowImpactRollbacks) {
-            VersionedPackage firstRollback =
-                    rollback.getPackages().get(0).getVersionRolledBackFrom();
-            rollbackPackage(rollback, firstRollback, rollbackReason);
-        }
-    }
-
-    private List<RollbackInfo> getRollbacksAvailableForImpactLevel(
-            List<RollbackInfo> availableRollbacks, int impactLevel) {
-        return availableRollbacks.stream()
-                .filter(rollbackInfo -> rollbackInfo.getRollbackImpactLevel() == impactLevel)
-                .toList();
-    }
-
-    private int getMinRollbackImpactLevel(List<RollbackInfo> availableRollbacks) {
-        return availableRollbacks.stream()
-                .mapToInt(RollbackInfo::getRollbackImpactLevel)
-                .min()
-                .orElse(-1);
-    }
-
-    private int getUserImpactBasedOnRollbackImpactLevel(List<RollbackInfo> availableRollbacks) {
-        int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
-        int minImpact = getMinRollbackImpactLevel(availableRollbacks);
-        switch (minImpact) {
-            case PackageManager.ROLLBACK_USER_IMPACT_LOW:
-                impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70;
-                break;
-            case PackageManager.ROLLBACK_USER_IMPACT_HIGH:
-                if (!SystemProperties.getBoolean(PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG, false)) {
-                    impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_90;
-                }
-                break;
-            default:
-                impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
-        }
-        return impact;
-    }
-
-    @VisibleForTesting
-    Handler getHandler() {
-        return mHandler;
-    }
-}
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/rollback/WatchdogRollbackLogger.java b/packages/CrashRecovery/services/module/java/com/android/server/rollback/WatchdogRollbackLogger.java
deleted file mode 100644
index 9cfed02..0000000
--- a/packages/CrashRecovery/services/module/java/com/android/server/rollback/WatchdogRollbackLogger.java
+++ /dev/null
@@ -1,255 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.rollback;
-
-import static com.android.server.crashrecovery.CrashRecoveryUtils.logCrashRecoveryEvent;
-import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH;
-import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING;
-import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_BOOT_LOOPING;
-import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK;
-import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH;
-import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT;
-import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN;
-import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED;
-import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE;
-import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE;
-import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageManager;
-import android.content.pm.VersionedPackage;
-import android.content.rollback.PackageRollbackInfo;
-import android.content.rollback.RollbackInfo;
-import android.os.SystemProperties;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.Slog;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.PackageWatchdog;
-import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog;
-
-import java.util.List;
-
-/**
- * This class handles the logic for logging Watchdog-triggered rollback events.
- * @hide
- */
-public final class WatchdogRollbackLogger {
-    private static final String TAG = "WatchdogRollbackLogger";
-
-    private static final String LOGGING_PARENT_KEY = "android.content.pm.LOGGING_PARENT";
-
-    private WatchdogRollbackLogger() {
-    }
-
-    @Nullable
-    private static String getLoggingParentName(Context context, @NonNull String packageName) {
-        PackageManager packageManager = context.getPackageManager();
-        try {
-            int flags = PackageManager.MATCH_APEX | PackageManager.GET_META_DATA;
-            ApplicationInfo ai = packageManager.getPackageInfo(packageName, flags).applicationInfo;
-            if (ai.metaData == null) {
-                return null;
-            }
-            return ai.metaData.getString(LOGGING_PARENT_KEY);
-        } catch (Exception e) {
-            Slog.w(TAG, "Unable to discover logging parent package: " + packageName, e);
-            return null;
-        }
-    }
-
-    /**
-     * Returns the logging parent of a given package if it exists, {@code null} otherwise.
-     *
-     * The logging parent is defined by the {@code android.content.pm.LOGGING_PARENT} field in the
-     * metadata of a package's AndroidManifest.xml.
-     */
-    @VisibleForTesting
-    @Nullable
-    static VersionedPackage getLogPackage(Context context,
-            @NonNull VersionedPackage failingPackage) {
-        String logPackageName;
-        VersionedPackage loggingParent;
-        logPackageName = getLoggingParentName(context, failingPackage.getPackageName());
-        if (logPackageName == null) {
-            return null;
-        }
-        try {
-            loggingParent = new VersionedPackage(logPackageName, context.getPackageManager()
-                    .getPackageInfo(logPackageName, 0 /* flags */).getLongVersionCode());
-        } catch (PackageManager.NameNotFoundException e) {
-            return null;
-        }
-        return loggingParent;
-    }
-
-    static void logRollbackStatusOnBoot(Context context, int rollbackId, String logPackageName,
-            List<RollbackInfo> recentlyCommittedRollbacks) {
-        PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
-
-        RollbackInfo rollback = null;
-        for (RollbackInfo info : recentlyCommittedRollbacks) {
-            if (rollbackId == info.getRollbackId()) {
-                rollback = info;
-                break;
-            }
-        }
-
-        if (rollback == null) {
-            Slog.e(TAG, "rollback info not found for last staged rollback: " + rollbackId);
-            return;
-        }
-
-        // Use the version of the logging parent that was installed before
-        // we rolled back for logging purposes.
-        VersionedPackage oldLoggingPackage = null;
-        if (!TextUtils.isEmpty(logPackageName)) {
-            for (PackageRollbackInfo packageRollback : rollback.getPackages()) {
-                if (logPackageName.equals(packageRollback.getPackageName())) {
-                    oldLoggingPackage = packageRollback.getVersionRolledBackFrom();
-                    break;
-                }
-            }
-        }
-
-        int sessionId = rollback.getCommittedSessionId();
-        PackageInstaller.SessionInfo sessionInfo = packageInstaller.getSessionInfo(sessionId);
-        if (sessionInfo == null) {
-            Slog.e(TAG, "On boot completed, could not load session id " + sessionId);
-            return;
-        }
-
-        if (sessionInfo.isStagedSessionApplied()) {
-            logEvent(oldLoggingPackage,
-                    WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS,
-                    WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN, "");
-        } else if (sessionInfo.isStagedSessionFailed()) {
-            logEvent(oldLoggingPackage,
-                    WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE,
-                    WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN, "");
-        }
-    }
-
-    /**
-     * Log a Watchdog rollback event to statsd.
-     *
-     * @param logPackage the package to associate the rollback with.
-     * @param type the state of the rollback.
-     * @param rollbackReason the reason Watchdog triggered a rollback, if known.
-     * @param failingPackageName the failing package or process which triggered the rollback.
-     */
-    public static void logEvent(@Nullable VersionedPackage logPackage, int type,
-            int rollbackReason, @NonNull String failingPackageName) {
-        String logMsg = "Watchdog event occurred with type: " + rollbackTypeToString(type)
-                + " logPackage: " + logPackage
-                + " rollbackReason: " + rollbackReasonToString(rollbackReason)
-                + " failedPackageName: " + failingPackageName;
-        Slog.i(TAG, logMsg);
-        if (logPackage != null) {
-            CrashRecoveryStatsLog.write(
-                    CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED,
-                    type,
-                    logPackage.getPackageName(),
-                    logPackage.getVersionCode(),
-                    rollbackReason,
-                    failingPackageName,
-                    new byte[]{});
-        } else {
-            // In the case that the log package is null, still log an empty string as an
-            // indication that retrieving the logging parent failed.
-            CrashRecoveryStatsLog.write(
-                    CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED,
-                    type,
-                    "",
-                    0,
-                    rollbackReason,
-                    failingPackageName,
-                    new byte[]{});
-        }
-
-        logTestProperties(logMsg);
-    }
-
-    /**
-     * Writes properties which will be used by rollback tests to check if particular rollback
-     * events have occurred.
-     */
-    private static void logTestProperties(String logMsg) {
-        // This property should be on only during the tests
-        if (!SystemProperties.getBoolean("persist.sys.rollbacktest.enabled", false)) {
-            return;
-        }
-        logCrashRecoveryEvent(Log.DEBUG, logMsg);
-    }
-
-    @VisibleForTesting
-    static int mapFailureReasonToMetric(@PackageWatchdog.FailureReasons int failureReason) {
-        switch (failureReason) {
-            case PackageWatchdog.FAILURE_REASON_NATIVE_CRASH:
-                return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH;
-            case PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK:
-                return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK;
-            case PackageWatchdog.FAILURE_REASON_APP_CRASH:
-                return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH;
-            case PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING:
-                return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING;
-            case PackageWatchdog.FAILURE_REASON_BOOT_LOOP:
-                return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_BOOT_LOOPING;
-            default:
-                return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN;
-        }
-    }
-
-    private static String rollbackTypeToString(int type) {
-        switch (type) {
-            case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE:
-                return "ROLLBACK_INITIATE";
-            case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS:
-                return "ROLLBACK_SUCCESS";
-            case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE:
-                return "ROLLBACK_FAILURE";
-            case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED:
-                return "ROLLBACK_BOOT_TRIGGERED";
-            default:
-                return "UNKNOWN";
-        }
-    }
-
-    private static String rollbackReasonToString(int reason) {
-        switch (reason) {
-            case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH:
-                return "REASON_NATIVE_CRASH";
-            case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK:
-                return "REASON_EXPLICIT_HEALTH_CHECK";
-            case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH:
-                return "REASON_APP_CRASH";
-            case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING:
-                return "REASON_APP_NOT_RESPONDING";
-            case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT:
-                return "REASON_NATIVE_CRASH_DURING_BOOT";
-            case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_BOOT_LOOPING:
-                return "REASON_BOOT_LOOP";
-            default:
-                return "UNKNOWN";
-        }
-    }
-}
diff --git a/packages/CrashRecovery/services/module/java/com/android/util/ArrayUtils.java b/packages/CrashRecovery/services/module/java/com/android/util/ArrayUtils.java
deleted file mode 100644
index 0b7b986..0000000
--- a/packages/CrashRecovery/services/module/java/com/android/util/ArrayUtils.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.util;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import java.io.File;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * Copied over from frameworks/base/core/java/com/android/internal/util/ArrayUtils.java
- *
- * @hide
- */
-public class ArrayUtils {
-    private ArrayUtils() { /* cannot be instantiated */ }
-    public static final File[] EMPTY_FILE = new File[0];
-
-
-    /**
-     * Return first index of {@code value} in {@code array}, or {@code -1} if
-     * not found.
-     */
-    public static <T> int indexOf(@Nullable T[] array, T value) {
-        if (array == null) return -1;
-        for (int i = 0; i < array.length; i++) {
-            if (Objects.equals(array[i], value)) return i;
-        }
-        return -1;
-    }
-
-    /** @hide */
-    public static @NonNull File[] defeatNullable(@Nullable File[] val) {
-        return (val != null) ? val : EMPTY_FILE;
-    }
-
-    /**
-     * Checks if given array is null or has zero elements.
-     */
-    public static boolean isEmpty(@Nullable int[] array) {
-        return array == null || array.length == 0;
-    }
-
-    /**
-     * True if the byte array is null or has length 0.
-     */
-    public static boolean isEmpty(@Nullable byte[] array) {
-        return array == null || array.length == 0;
-    }
-
-    /**
-     * Converts from List of bytes to byte array
-     * @param list
-     * @return byte[]
-     */
-    public static byte[] toPrimitive(List<byte[]> list) {
-        if (list.size() == 0) {
-            return new byte[0];
-        }
-        int byteLen = list.get(0).length;
-        byte[] array = new byte[list.size() * byteLen];
-        for (int i = 0; i < list.size(); i++) {
-            for (int j = 0; j < list.get(i).length; j++) {
-                array[i * byteLen + j] = list.get(i)[j];
-            }
-        }
-        return array;
-    }
-
-    /**
-     * Adds value to given array if not already present, providing set-like
-     * behavior.
-     */
-    public static @NonNull int[] appendInt(@Nullable int[] cur, int val) {
-        return appendInt(cur, val, false);
-    }
-
-    /**
-     * Adds value to given array.
-     */
-    public static @NonNull int[] appendInt(@Nullable int[] cur, int val,
-            boolean allowDuplicates) {
-        if (cur == null) {
-            return new int[] { val };
-        }
-        final int n = cur.length;
-        if (!allowDuplicates) {
-            for (int i = 0; i < n; i++) {
-                if (cur[i] == val) {
-                    return cur;
-                }
-            }
-        }
-        int[] ret = new int[n + 1];
-        System.arraycopy(cur, 0, ret, 0, n);
-        ret[n] = val;
-        return ret;
-    }
-}
diff --git a/packages/CrashRecovery/services/module/java/com/android/util/FileUtils.java b/packages/CrashRecovery/services/module/java/com/android/util/FileUtils.java
deleted file mode 100644
index 9c73fee..0000000
--- a/packages/CrashRecovery/services/module/java/com/android/util/FileUtils.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.util;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import java.io.BufferedInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * Bits and pieces copied from hidden API of android.os.FileUtils.
- *
- * @hide
- */
-public class FileUtils {
-    /**
-     * Read a text file into a String, optionally limiting the length.
-     *
-     * @param file     to read (will not seek, so things like /proc files are OK)
-     * @param max      length (positive for head, negative of tail, 0 for no limit)
-     * @param ellipsis to add of the file was truncated (can be null)
-     * @return the contents of the file, possibly truncated
-     * @throws IOException if something goes wrong reading the file
-     * @hide
-     */
-    public static @Nullable String readTextFile(@Nullable File file, @Nullable int max,
-            @Nullable String ellipsis) throws IOException {
-        InputStream input = new FileInputStream(file);
-        // wrapping a BufferedInputStream around it because when reading /proc with unbuffered
-        // input stream, bytes read not equal to buffer size is not necessarily the correct
-        // indication for EOF; but it is true for BufferedInputStream due to its implementation.
-        BufferedInputStream bis = new BufferedInputStream(input);
-        try {
-            long size = file.length();
-            if (max > 0 || (size > 0 && max == 0)) {  // "head" mode: read the first N bytes
-                if (size > 0 && (max == 0 || size < max)) max = (int) size;
-                byte[] data = new byte[max + 1];
-                int length = bis.read(data);
-                if (length <= 0) return "";
-                if (length <= max) return new String(data, 0, length);
-                if (ellipsis == null) return new String(data, 0, max);
-                return new String(data, 0, max) + ellipsis;
-            } else if (max < 0) {  // "tail" mode: keep the last N
-                int len;
-                boolean rolled = false;
-                byte[] last = null;
-                byte[] data = null;
-                do {
-                    if (last != null) rolled = true;
-                    byte[] tmp = last;
-                    last = data;
-                    data = tmp;
-                    if (data == null) data = new byte[-max];
-                    len = bis.read(data);
-                } while (len == data.length);
-
-                if (last == null && len <= 0) return "";
-                if (last == null) return new String(data, 0, len);
-                if (len > 0) {
-                    rolled = true;
-                    System.arraycopy(last, len, last, 0, last.length - len);
-                    System.arraycopy(data, 0, last, last.length - len, len);
-                }
-                if (ellipsis == null || !rolled) return new String(last);
-                return ellipsis + new String(last);
-            } else {  // "cat" mode: size unknown, read it all in streaming fashion
-                ByteArrayOutputStream contents = new ByteArrayOutputStream();
-                int len;
-                byte[] data = new byte[1024];
-                do {
-                    len = bis.read(data);
-                    if (len > 0) contents.write(data, 0, len);
-                } while (len == data.length);
-                return contents.toString();
-            }
-        } finally {
-            bis.close();
-            input.close();
-        }
-    }
-
-    /**
-     * Perform an fsync on the given FileOutputStream. The stream at this
-     * point must be flushed but not yet closed.
-     *
-     * @hide
-     */
-    public static boolean sync(FileOutputStream stream) {
-        try {
-            if (stream != null) {
-                stream.getFD().sync();
-            }
-            return true;
-        } catch (IOException e) {
-        }
-        return false;
-    }
-
-    /**
-     * List the files in the directory or return empty file.
-     *
-     * @hide
-     */
-    public static @NonNull File[] listFilesOrEmpty(@Nullable File dir) {
-        return (dir != null) ? ArrayUtils.defeatNullable(dir.listFiles())
-            : ArrayUtils.EMPTY_FILE;
-    }
-}
diff --git a/packages/CrashRecovery/services/module/java/com/android/util/LongArrayQueue.java b/packages/CrashRecovery/services/module/java/com/android/util/LongArrayQueue.java
deleted file mode 100644
index 9a24ada..0000000
--- a/packages/CrashRecovery/services/module/java/com/android/util/LongArrayQueue.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.util;
-
-import libcore.util.EmptyArray;
-
-import java.util.NoSuchElementException;
-
-/**
- * Copied from frameworks/base/core/java/android/util/LongArrayQueue.java
- *
- * @hide
- */
-public class LongArrayQueue {
-
-    private long[] mValues;
-    private int mSize;
-    private int mHead;
-    private int mTail;
-
-    private long[] newUnpaddedLongArray(int num) {
-        return new long[num];
-    }
-    /**
-     * Initializes a queue with the given starting capacity.
-     *
-     * @param initialCapacity the capacity.
-     */
-    public LongArrayQueue(int initialCapacity) {
-        if (initialCapacity == 0) {
-            mValues = EmptyArray.LONG;
-        } else {
-            mValues = newUnpaddedLongArray(initialCapacity);
-        }
-        mSize = 0;
-        mHead = mTail = 0;
-    }
-
-    /**
-     * Initializes a queue with default starting capacity.
-     */
-    public LongArrayQueue() {
-        this(16);
-    }
-
-    /** @hide */
-    public static int growSize(int currentSize) {
-        return currentSize <= 4 ? 8 : currentSize * 2;
-    }
-
-    private void grow() {
-        if (mSize < mValues.length) {
-            throw new IllegalStateException("Queue not full yet!");
-        }
-        final int newSize = growSize(mSize);
-        final long[] newArray = newUnpaddedLongArray(newSize);
-        final int r = mValues.length - mHead; // Number of elements on and to the right of head.
-        System.arraycopy(mValues, mHead, newArray, 0, r);
-        System.arraycopy(mValues, 0, newArray, r, mHead);
-        mValues = newArray;
-        mHead = 0;
-        mTail = mSize;
-    }
-
-    /**
-     * Returns the number of elements in the queue.
-     */
-    public int size() {
-        return mSize;
-    }
-
-    /**
-     * Removes all elements from this queue.
-     */
-    public void clear() {
-        mSize = 0;
-        mHead = mTail = 0;
-    }
-
-    /**
-     * Adds a value to the tail of the queue.
-     *
-     * @param value the value to be added.
-     */
-    public void addLast(long value) {
-        if (mSize == mValues.length) {
-            grow();
-        }
-        mValues[mTail] = value;
-        mTail = (mTail + 1) % mValues.length;
-        mSize++;
-    }
-
-    /**
-     * Removes an element from the head of the queue.
-     *
-     * @return the element at the head of the queue.
-     * @throws NoSuchElementException if the queue is empty.
-     */
-    public long removeFirst() {
-        if (mSize == 0) {
-            throw new NoSuchElementException("Queue is empty!");
-        }
-        final long ret = mValues[mHead];
-        mHead = (mHead + 1) % mValues.length;
-        mSize--;
-        return ret;
-    }
-
-    /**
-     * Returns the element at the given position from the head of the queue, where 0 represents the
-     * head of the queue.
-     *
-     * @param position the position from the head of the queue.
-     * @return the element found at the given position.
-     * @throws IndexOutOfBoundsException if {@code position} < {@code 0} or
-     *                                   {@code position} >= {@link #size()}
-     */
-    public long get(int position) {
-        if (position < 0 || position >= mSize) {
-            throw new IndexOutOfBoundsException("Index " + position
-                + " not valid for a queue of size " + mSize);
-        }
-        final int index = (mHead + position) % mValues.length;
-        return mValues[index];
-    }
-
-    /**
-     * Returns the element at the head of the queue, without removing it.
-     *
-     * @return the element at the head of the queue.
-     * @throws NoSuchElementException if the queue is empty
-     */
-    public long peekFirst() {
-        if (mSize == 0) {
-            throw new NoSuchElementException("Queue is empty!");
-        }
-        return mValues[mHead];
-    }
-
-    /**
-     * Returns the element at the tail of the queue.
-     *
-     * @return the element at the tail of the queue.
-     * @throws NoSuchElementException if the queue is empty.
-     */
-    public long peekLast() {
-        if (mSize == 0) {
-            throw new NoSuchElementException("Queue is empty!");
-        }
-        final int index = (mTail == 0) ? mValues.length - 1 : mTail - 1;
-        return mValues[index];
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public String toString() {
-        if (mSize <= 0) {
-            return "{}";
-        }
-
-        final StringBuilder buffer = new StringBuilder(mSize * 64);
-        buffer.append('{');
-        buffer.append(get(0));
-        for (int i = 1; i < mSize; i++) {
-            buffer.append(", ");
-            buffer.append(get(i));
-        }
-        buffer.append('}');
-        return buffer.toString();
-    }
-}
diff --git a/packages/CrashRecovery/services/module/java/com/android/util/XmlUtils.java b/packages/CrashRecovery/services/module/java/com/android/util/XmlUtils.java
deleted file mode 100644
index 50823f5..0000000
--- a/packages/CrashRecovery/services/module/java/com/android/util/XmlUtils.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.util;
-
-import android.annotation.NonNull;
-import android.system.ErrnoException;
-import android.system.Os;
-
-import com.android.modules.utils.TypedXmlPullParser;
-
-import libcore.util.XmlObjectFactory;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.BufferedInputStream;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- *  Bits and pieces copied from hidden API of
- *  frameworks/base/core/java/com/android/internal/util/XmlUtils.java
- *
- * @hide
- */
-public class XmlUtils {
-
-    private static final String STRING_ARRAY_SEPARATOR = ":";
-
-    /** @hide */
-    public static final void beginDocument(XmlPullParser parser, String firstElementName)
-            throws XmlPullParserException, IOException {
-        int type;
-        while ((type = parser.next()) != parser.START_TAG
-            && type != parser.END_DOCUMENT) {
-            // Do nothing
-        }
-
-        if (type != parser.START_TAG) {
-            throw new XmlPullParserException("No start tag found");
-        }
-
-        if (!parser.getName().equals(firstElementName)) {
-            throw new XmlPullParserException("Unexpected start tag: found " + parser.getName()
-                + ", expected " + firstElementName);
-        }
-    }
-
-    /** @hide */
-    public static boolean nextElementWithin(XmlPullParser parser, int outerDepth)
-            throws IOException, XmlPullParserException {
-        for (;;) {
-            int type = parser.next();
-            if (type == XmlPullParser.END_DOCUMENT
-                    || (type == XmlPullParser.END_TAG && parser.getDepth() == outerDepth)) {
-                return false;
-            }
-            if (type == XmlPullParser.START_TAG
-                    && parser.getDepth() == outerDepth + 1) {
-                return true;
-            }
-        }
-    }
-
-    private static XmlPullParser newPullParser() {
-        try {
-            XmlPullParser parser = XmlObjectFactory.newXmlPullParser();
-            parser.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, true);
-            parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
-            return parser;
-        } catch (XmlPullParserException e) {
-            throw new AssertionError();
-        }
-    }
-
-    /** @hide */
-    public static @NonNull TypedXmlPullParser resolvePullParser(@NonNull InputStream in)
-            throws IOException {
-        final byte[] magic = new byte[4];
-        if (in instanceof FileInputStream) {
-            try {
-                Os.pread(((FileInputStream) in).getFD(), magic, 0, magic.length, 0);
-            } catch (ErrnoException e) {
-                throw e.rethrowAsIOException();
-            }
-        } else {
-            if (!in.markSupported()) {
-                in = new BufferedInputStream(in);
-            }
-            in.mark(8);
-            in.read(magic);
-            in.reset();
-        }
-
-        final TypedXmlPullParser xml;
-        xml = (TypedXmlPullParser) newPullParser();
-        try {
-            xml.setInput(in, "UTF_8");
-        } catch (XmlPullParserException e) {
-            throw new IOException(e);
-        }
-        return xml;
-    }
-}
diff --git a/packages/EasterEgg/AndroidManifest.xml b/packages/EasterEgg/AndroidManifest.xml
index 754abb2..96e5892 100644
--- a/packages/EasterEgg/AndroidManifest.xml
+++ b/packages/EasterEgg/AndroidManifest.xml
@@ -33,7 +33,7 @@
     <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
 
     <application
-        android:icon="@drawable/android15_patch_adaptive"
+        android:icon="@drawable/android16_patch_adaptive"
         android:label="@string/app_name">
 
         <!-- Android V easter egg: Daydream version of Landroid
@@ -41,7 +41,7 @@
         <service
             android:name=".landroid.DreamUniverse"
             android:exported="true"
-            android:icon="@drawable/android15_patch_adaptive"
+            android:icon="@drawable/android16_patch_adaptive"
             android:label="@string/v_egg_name"
             android:description="@string/dream_description"
             android:enabled="false"
@@ -62,7 +62,7 @@
             android:name=".landroid.MainActivity"
             android:exported="true"
             android:label="@string/u_egg_name"
-            android:icon="@drawable/android15_patch_adaptive"
+            android:icon="@drawable/android16_patch_adaptive"
             android:configChanges="orientation|screenLayout|screenSize|density"
             android:theme="@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen">
             <intent-filter>
diff --git a/packages/EasterEgg/res/drawable/android16_patch_adaptive.xml b/packages/EasterEgg/res/drawable/android16_patch_adaptive.xml
new file mode 100644
index 0000000..277df47
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/android16_patch_adaptive.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (C) 2024 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/android16_patch_adaptive_background"/>
+    <foreground android:drawable="@drawable/android16_patch_adaptive_foreground"/>
+    <monochrome android:drawable="@drawable/android16_patch_monochrome"/>
+</adaptive-icon>
diff --git a/packages/EasterEgg/res/drawable/android16_patch_adaptive_background.xml b/packages/EasterEgg/res/drawable/android16_patch_adaptive_background.xml
new file mode 100644
index 0000000..17c2b92
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/android16_patch_adaptive_background.xml
@@ -0,0 +1,245 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (C) 2024 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+  <group>
+    <clip-path
+        android:pathData="M0,0h108v108h-108z"/>
+    <path
+        android:pathData="M73,54L54,35L35,54L54,73L73,54Z"
+        android:fillColor="#34A853"/>
+    <path
+        android:pathData="M44.5,44.5L54,44.5L44.5,54L44.5,44.5Z"
+        android:fillColor="#1F8E3D"/>
+    <path
+        android:pathData="M63.5,63.5L54,63.5L63.5,54L63.5,63.5Z"
+        android:fillColor="#1F8E3D"/>
+    <path
+        android:pathData="M54,54L54,44.5L63.5,54L54,54Z"
+        android:fillColor="#1F8E3D"/>
+    <path
+        android:pathData="M54,44.5L54,35L63.5,44.5L54,44.5Z"
+        android:fillColor="#1F8E3D"/>
+    <path
+        android:pathData="M54,63.5L54,73L44.5,63.5L54,63.5Z"
+        android:fillColor="#1F8E3D"/>
+    <path
+        android:pathData="M63.5,54L63.5,44.5L73,54L63.5,54Z"
+        android:fillColor="#1F8E3D"/>
+    <path
+        android:pathData="M44.5,54L44.5,63.5L35,54L44.5,54Z"
+        android:fillColor="#1F8E3D"/>
+    <path
+        android:pathData="M54,54L54,63.5L44.5,54L54,54Z"
+        android:fillColor="#1F8E3D"/>
+    <path
+        android:pathData="M82.5,25.5L82.5,35L73,25.5L82.5,25.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M63.5,44.5L63.5,35L73,44.5L63.5,44.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M73,35L82.5,35L73,44.5L73,35Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M82.5,35L92,35L82.5,44.5L82.5,35Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M63.5,35L54,35L63.5,25.5L63.5,35Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M73,44.5L82.5,44.5L73,54L73,44.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M73,25.5L63.5,25.5L73,16L73,25.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M73,35L63.5,35L73,25.5L73,35Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M82.5,63.5L82.5,73L73,63.5L82.5,63.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M63.5,82.5L63.5,73L73,82.5L63.5,82.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M73,73L82.5,73L73,82.5L73,73Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M82.5,73L92,73L82.5,82.5L82.5,73Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M63.5,73L54,73L63.5,63.5L63.5,73Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M73,82.5L82.5,82.5L73,92L73,82.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M73,63.5L63.5,63.5L73,54L73,63.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M73,73L63.5,73L73,63.5L73,73Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M44.5,63.5L44.5,73L35,63.5L44.5,63.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M25.5,82.5L25.5,73L35,82.5L25.5,82.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M35,73L44.5,73L35,82.5L35,73Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M44.5,73L54,73L44.5,82.5L44.5,73Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M25.5,73L16,73L25.5,63.5L25.5,73Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M35,82.5L44.5,82.5L35,92L35,82.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M35,63.5L25.5,63.5L35,54L35,63.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M35,73L25.5,73L35,63.5L35,73Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M44.5,25.5L44.5,35L35,25.5L44.5,25.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M25.5,44.5L25.5,35L35,44.5L25.5,44.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M35,35L44.5,35L35,44.5L35,35Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M44.5,35L54,35L44.5,44.5L44.5,35Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M25.5,35L16,35L25.5,25.5L25.5,35Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M35,44.5L44.5,44.5L35,54L35,44.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M35,25.5L25.5,25.5L35,16L35,25.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M35,35L25.5,35L35,25.5L35,35Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M63.5,25.5L54,25.5L63.5,16L63.5,25.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M44.5,6.5L54,6.5L44.5,16L44.5,6.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M54,16L54,25.5L44.5,16L54,16Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M54,25.5L54,35L44.5,25.5L54,25.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M54,6.5L54,-3L63.5,6.5L54,6.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M44.5,16L44.5,25.5L35,16L44.5,16Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M63.5,16L63.5,6.5L73,16L63.5,16Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M54,16L54,6.5L63.5,16L54,16Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M101.5,63.5L92,63.5L101.5,54L101.5,63.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M82.5,44.5L92,44.5L82.5,54L82.5,44.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M92,54L92,63.5L82.5,54L92,54Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M92,63.5L92,73L82.5,63.5L92,63.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M92,44.5L92,35L101.5,44.5L92,44.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M82.5,54L82.5,63.5L73,54L82.5,54Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M101.5,54L101.5,44.5L111,54L101.5,54Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M92,54L92,44.5L101.5,54L92,54Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M63.5,101.5L54,101.5L63.5,92L63.5,101.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M44.5,82.5L54,82.5L44.5,92L44.5,82.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M54,92L54,101.5L44.5,92L54,92Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M54,101.5L54,111L44.5,101.5L54,101.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M54,82.5L54,73L63.5,82.5L54,82.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M44.5,92L44.5,101.5L35,92L44.5,92Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M63.5,92L63.5,82.5L73,92L63.5,92Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M54,92L54,82.5L63.5,92L54,92Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M25.5,63.5L16,63.5L25.5,54L25.5,63.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M6.5,44.5L16,44.5L6.5,54L6.5,44.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M16,54L16,63.5L6.5,54L16,54Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M16,63.5L16,73L6.5,63.5L16,63.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M16,44.5L16,35L25.5,44.5L16,44.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M6.5,54L6.5,63.5L-3,54L6.5,54Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M25.5,54L25.5,44.5L35,54L25.5,54Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M16,54L16,44.5L25.5,54L16,54Z"
+        android:fillColor="#16161D"/>
+  </group>
+</vector>
diff --git a/packages/EasterEgg/res/drawable/android16_patch_adaptive_foreground.xml b/packages/EasterEgg/res/drawable/android16_patch_adaptive_foreground.xml
new file mode 100644
index 0000000..4c29323
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/android16_patch_adaptive_foreground.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (C) 2024 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+  <path
+      android:pathData="M40.65,63.013C40.722,62.922 40.716,62.789 40.633,62.707V62.707C40.537,62.61 40.377,62.62 40.292,62.727C34.567,69.881 31.569,75.536 33.089,77.056C35.366,79.333 46.923,71.469 58.901,59.491C60.049,58.343 61.159,57.199 62.226,56.066C62.342,55.943 62.339,55.751 62.219,55.632L61.566,54.978C61.441,54.854 61.238,54.857 61.117,54.985C60.057,56.11 58.951,57.25 57.806,58.395C46.882,69.319 36.496,76.646 34.61,74.759C33.417,73.567 35.903,68.982 40.65,63.013Z"
+      android:fillColor="#C6FF00"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M67.956,52.033C68.205,51.966 68.462,52.115 68.529,52.364C68.596,52.614 68.448,52.871 68.198,52.938L67.956,52.033ZM68.198,52.938L63.926,54.083L63.683,53.178L67.956,52.033L68.198,52.938Z"
+      android:fillColor="#000000"/>
+  <path
+      android:pathData="M64.497,49.237C64.564,48.987 64.821,48.839 65.071,48.906C65.32,48.973 65.469,49.229 65.402,49.479L64.497,49.237ZM65.402,49.479L64.257,53.752L63.352,53.509L64.497,49.237L65.402,49.479Z"
+      android:fillColor="#000000"/>
+  <path
+      android:pathData="M66.145,51.236C64.869,49.961 62.83,49.931 61.591,51.17L58.825,53.937C58.585,54.176 58.585,54.564 58.825,54.803C59.063,55.042 59.452,55.042 59.691,54.803L60.436,54.057C60.915,53.579 61.69,53.579 62.169,54.057L63.324,55.212C63.802,55.691 63.802,56.466 63.324,56.945L62.578,57.69C62.339,57.929 62.339,58.318 62.578,58.557C62.817,58.796 63.205,58.796 63.444,58.557L66.211,55.79C67.45,54.551 67.42,52.512 66.145,51.236Z"
+      android:fillColor="#000000"/>
+</vector>
diff --git a/packages/EasterEgg/res/drawable/android16_patch_monochrome.xml b/packages/EasterEgg/res/drawable/android16_patch_monochrome.xml
new file mode 100644
index 0000000..608d5ea
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/android16_patch_monochrome.xml
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (C) 2024 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+  <path
+      android:strokeWidth="1"
+      android:pathData="M54.707,35.707L72.293,53.293A1,1 102.155,0 1,72.293 54.707L54.707,72.293A1,1 0,0 1,53.293 72.293L35.707,54.707A1,1 0,0 1,35.707 53.293L53.293,35.707A1,1 0,0 1,54.707 35.707z"
+      android:fillColor="#00000000"
+      android:strokeColor="#ffffff"/>
+  <path
+      android:pathData="M55.237,35.177L72.823,52.763A1.75,1.75 67.835,0 1,72.823 55.237L55.237,72.823A1.75,1.75 77.684,0 1,52.763 72.823L35.177,55.237A1.75,1.75 0,0 1,35.177 52.763L52.763,35.177A1.75,1.75 0,0 1,55.237 35.177z"
+      android:strokeWidth="1.5"
+      android:fillColor="#00000000"
+      android:strokeColor="#ffffff"/>
+  <path
+      android:pathData="M44.5,44.5h19v19h-19z"
+      android:strokeWidth="0.75"
+      android:fillColor="#00000000"
+      android:strokeColor="#ffffff"/>
+  <path
+      android:pathData="M54,44.5l9.5,9.5l-9.5,9.5l-9.5,-9.5z"
+      android:strokeWidth="0.75"
+      android:fillColor="#00000000"
+      android:strokeColor="#ffffff"/>
+  <path
+      android:pathData="M54,35V73"
+      android:strokeWidth="0.75"
+      android:fillColor="#00000000"
+      android:strokeColor="#ffffff"/>
+  <path
+      android:pathData="M73,54L35,54"
+      android:strokeWidth="0.75"
+      android:fillColor="#00000000"
+      android:strokeColor="#ffffff"/>
+  <path
+      android:pathData="M33.576,31.135l1.146,1.146l-1.146,1.146l-1.146,-1.146z"
+      android:fillColor="#E8F5E9"/>
+  <path
+      android:pathData="M31.146,65.966l1.146,1.146l-1.146,1.146l-1.146,-1.146z"
+      android:fillColor="#E8F5E9"/>
+  <path
+      android:pathData="M26.718,56l1.718,1.718l-1.718,1.718l-1.718,-1.718z"
+      android:fillColor="#E8F5E9"/>
+  <path
+      android:pathData="M31.146,48l1.146,1.146l-1.146,1.146l-1.146,-1.146z"
+      android:fillColor="#E8F5E9"/>
+  <path
+      android:pathData="M41.925,34.374l1.718,1.718l-1.718,1.718l-1.718,-1.718z"
+      android:fillColor="#E8F5E9"/>
+  <path
+      android:pathData="M63.146,71l1.146,1.146l-1.146,1.146l-1.146,-1.146z"
+      android:fillColor="#E8F5E9"/>
+  <path
+      android:pathData="M48.567,74.553l1.718,1.718l-1.718,1.718l-1.718,-1.718z"
+      android:fillColor="#E8F5E9"/>
+  <path
+      android:pathData="M51.146,26l1.146,1.146l-1.146,1.146l-1.146,-1.146z"
+      android:fillColor="#E8F5E9"/>
+  <path
+      android:pathData="M72.291,32.146l-1.146,1.146l-1.146,-1.146l1.146,-1.146z"
+      android:fillColor="#E8F5E9"/>
+  <path
+      android:pathData="M76.531,36.417l-1.718,1.718l-1.718,-1.718l1.718,-1.718z"
+      android:fillColor="#E8F5E9"/>
+  <path
+      android:pathData="M58.291,32.146l-1.146,1.146l-1.146,-1.146l1.146,-1.146z"
+      android:fillColor="#E8F5E9"/>
+  <path
+      android:pathData="M68.419,36.978l-1.146,1.146l-1.146,-1.146l1.146,-1.146z"
+      android:fillColor="#E8F5E9"/>
+  <path
+      android:pathData="M74.252,64.034l-1.146,1.146l-1.146,-1.146l1.146,-1.146z"
+      android:fillColor="#E8F5E9"/>
+  <path
+      android:pathData="M71.437,76.718l-1.718,1.718l-1.718,-1.718l1.718,-1.718z"
+      android:fillColor="#E8F5E9"/>
+  <path
+      android:pathData="M42.984,69.38l-1.146,1.146l-1.146,-1.146l1.146,-1.146z"
+      android:fillColor="#E8F5E9"/>
+  <path
+      android:pathData="M82.437,51.718l-1.718,1.718l-1.718,-1.718l1.718,-1.718z"
+      android:fillColor="#E8F5E9"/>
+  <path
+      android:pathData="M40.65,63.013C40.722,62.922 40.716,62.789 40.633,62.707V62.707C40.537,62.61 40.377,62.62 40.292,62.727C34.567,69.881 31.569,75.536 33.089,77.056C35.366,79.333 46.923,71.469 58.901,59.491C60.049,58.343 61.159,57.199 62.226,56.066C62.342,55.943 62.339,55.751 62.219,55.632L61.566,54.978C61.441,54.854 61.238,54.857 61.117,54.985C60.057,56.11 58.951,57.25 57.806,58.395C46.882,69.319 36.496,76.646 34.61,74.759C33.417,73.567 35.903,68.982 40.65,63.013Z"
+      android:fillColor="#ffffff"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M67.956,52.033C68.205,51.966 68.462,52.115 68.529,52.364C68.596,52.614 68.448,52.871 68.198,52.938L67.956,52.033ZM68.198,52.938L63.926,54.083L63.683,53.178L67.956,52.033L68.198,52.938Z"
+      android:fillColor="#ffffff"/>
+  <path
+      android:pathData="M64.497,49.237C64.564,48.987 64.821,48.839 65.071,48.906C65.32,48.972 65.469,49.229 65.402,49.479L64.497,49.237ZM65.402,49.479L64.257,53.752L63.352,53.509L64.497,49.237L65.402,49.479Z"
+      android:fillColor="#ffffff"/>
+  <path
+      android:pathData="M66.145,51.236C64.869,49.961 62.83,49.931 61.591,51.17L58.825,53.937C58.585,54.176 58.585,54.564 58.825,54.803C59.063,55.042 59.452,55.042 59.691,54.803L60.436,54.057C60.915,53.579 61.69,53.579 62.169,54.057L63.324,55.212C63.802,55.691 63.802,56.466 63.324,56.945L62.578,57.69C62.339,57.929 62.339,58.318 62.578,58.556C62.817,58.796 63.205,58.796 63.444,58.556L66.211,55.79C67.45,54.551 67.42,52.512 66.145,51.236Z"
+      android:fillColor="#ffffff"/>
+</vector>
diff --git a/packages/EasterEgg/src/com/android/egg/landroid/DreamUniverse.kt b/packages/EasterEgg/src/com/android/egg/landroid/DreamUniverse.kt
index 8c87c5d..d56e8b9 100644
--- a/packages/EasterEgg/src/com/android/egg/landroid/DreamUniverse.kt
+++ b/packages/EasterEgg/src/com/android/egg/landroid/DreamUniverse.kt
@@ -59,7 +59,7 @@
     override fun onAttachedToWindow() {
         super.onAttachedToWindow()
 
-        val universe = VisibleUniverse(namer = Namer(resources), randomSeed = randomSeed())
+        val universe = Universe(namer = Namer(resources), randomSeed = randomSeed())
 
         isInteractive = false
 
diff --git a/packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt b/packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt
index 16ec1a9..4f77b00 100644
--- a/packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt
+++ b/packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt
@@ -26,7 +26,6 @@
 import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.animation.core.animateFloatAsState
 import androidx.compose.animation.core.withInfiniteAnimationFrameNanos
-import androidx.compose.foundation.Canvas
 import androidx.compose.foundation.border
 import androidx.compose.foundation.gestures.awaitFirstDown
 import androidx.compose.foundation.gestures.forEachGesture
@@ -45,6 +44,7 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.currentRecomposeScope
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -64,7 +64,6 @@
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.font.FontFamily
 import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.text.toUpperCase
 import androidx.compose.ui.tooling.preview.Devices
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
@@ -75,6 +74,9 @@
 import androidx.lifecycle.repeatOnLifecycle
 import androidx.window.layout.FoldingFeature
 import androidx.window.layout.WindowInfoTracker
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
 import java.lang.Float.max
 import java.lang.Float.min
 import java.util.Calendar
@@ -83,9 +85,6 @@
 import kotlin.math.floor
 import kotlin.math.sqrt
 import kotlin.random.Random
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
 
 enum class RandomSeedType {
     Fixed,
@@ -139,7 +138,6 @@
         else -> Build.VERSION.RELEASE_OR_CODENAME.replace(Regex("[a-z]*"), "")
     }
 
-
 val DEBUG_TEXT = mutableStateOf("Hello Universe")
 const val SHOW_DEBUG_TEXT = false
 
@@ -158,7 +156,7 @@
 }
 
 @Composable
-fun Telemetry(universe: VisibleUniverse) {
+fun Telemetry(universe: Universe) {
     var topVisible by remember { mutableStateOf(false) }
     var bottomVisible by remember { mutableStateOf(false) }
 
@@ -180,10 +178,15 @@
         topVisible = true
     }
 
-    universe.triggerDraw.value // recompose on every frame
-
     val explored = universe.planets.filter { it.explored }
 
+    // TODO: Narrow the scope of invalidation here to the specific data needed;
+    // the behavior below mimics the previous implementation of a snapshot ticker value
+    val recomposeScope = currentRecomposeScope
+    Telescope(universe) {
+        recomposeScope.invalidate()
+    }
+
     BoxWithConstraints(
         modifier =
             Modifier.fillMaxSize().padding(6.dp).windowInsetsPadding(WindowInsets.safeContent),
@@ -299,7 +302,7 @@
 
         enableEdgeToEdge()
 
-        val universe = VisibleUniverse(namer = Namer(resources), randomSeed = randomSeed())
+        val universe = Universe(namer = Namer(resources), randomSeed = randomSeed())
 
         if (TEST_UNIVERSE) {
             universe.initTest()
@@ -373,7 +376,7 @@
 @Preview(name = "tablet", device = Devices.TABLET)
 @Composable
 fun MainActivityPreview() {
-    val universe = VisibleUniverse(namer = Namer(Resources.getSystem()), randomSeed = randomSeed())
+    val universe = Universe(namer = Namer(Resources.getSystem()), randomSeed = randomSeed())
 
     universe.initTest()
 
@@ -458,12 +461,12 @@
 @Composable
 fun Spaaaace(
     modifier: Modifier,
-    u: VisibleUniverse,
+    u: Universe,
     foldState: MutableState<FoldingFeature?> = mutableStateOf(null)
 ) {
     LaunchedEffect(u) {
         while (true) withInfiniteAnimationFrameNanos { frameTimeNanos ->
-            u.simulateAndDrawFrame(frameTimeNanos)
+            u.step(frameTimeNanos)
         }
     }
 
@@ -492,7 +495,7 @@
     val centerFracY: Float by
         animateFloatAsState(if (halfFolded && horizontalFold) 0.25f else 0.5f, label = "centerY")
 
-    Canvas(modifier = canvasModifier) {
+    UniverseCanvas(u, canvasModifier) { u ->
         drawRect(Colors.Eigengrau, Offset.Zero, size)
 
         val closest = u.closestPlanet()
diff --git a/packages/EasterEgg/src/com/android/egg/landroid/Physics.kt b/packages/EasterEgg/src/com/android/egg/landroid/Physics.kt
index d14234e..b8c6881 100644
--- a/packages/EasterEgg/src/com/android/egg/landroid/Physics.kt
+++ b/packages/EasterEgg/src/com/android/egg/landroid/Physics.kt
@@ -17,6 +17,8 @@
 package com.android.egg.landroid
 
 import android.util.ArraySet
+import androidx.compose.ui.util.fastForEach
+import kotlinx.coroutines.DisposableHandle
 import kotlin.random.Random
 
 // artificially speed up or slow down the simulation
@@ -127,6 +129,7 @@
     val rng = Random(randomSeed)
     val entities = ArraySet<Entity>(1000)
     val constraints = ArraySet<Constraint>(100)
+    private val simStepListeners = mutableListOf<() -> Unit>()
 
     fun add(e: Entity) = entities.add(e)
     fun remove(e: Entity) = entities.remove(e)
@@ -169,5 +172,26 @@
 
         // 3. compute new velocities from updated positions and saved positions
         postUpdateAll(dt, localEntities)
+
+        // 4. notify listeners that step is complete
+        simStepListeners.fastForEach { it.invoke() }
+    }
+
+    /**
+     * Register [listener] to be invoked every time the [Simulator] completes one [step].
+     * Use this to enqueue drawing.
+     *
+     * Instead of the usual register()/unregister() pattern, we're going to borrow
+     * [kotlinx.coroutines.DisposableHandle] here. Call [DisposableHandle.dispose] on the return
+     * value to unregister.
+     */
+    fun addSimulationStepListener(listener: () -> Unit): DisposableHandle {
+        // add to listener list
+        simStepListeners += listener
+
+        return DisposableHandle {
+            // on dispose, remove from listener list
+            simStepListeners -= listener
+        }
     }
 }
diff --git a/packages/EasterEgg/src/com/android/egg/landroid/VisibleUniverse.kt b/packages/EasterEgg/src/com/android/egg/landroid/VisibleUniverse.kt
index ed3ebc7..c476d5c 100644
--- a/packages/EasterEgg/src/com/android/egg/landroid/VisibleUniverse.kt
+++ b/packages/EasterEgg/src/com/android/egg/landroid/VisibleUniverse.kt
@@ -16,19 +16,31 @@
 
 package com.android.egg.landroid
 
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.RememberObserver
+import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Path
 import androidx.compose.ui.graphics.PathEffect
 import androidx.compose.ui.graphics.PointMode
+import androidx.compose.ui.graphics.drawscope.ContentDrawScope
 import androidx.compose.ui.graphics.drawscope.DrawScope
 import androidx.compose.ui.graphics.drawscope.Stroke
 import androidx.compose.ui.graphics.drawscope.rotateRad
 import androidx.compose.ui.graphics.drawscope.scale
 import androidx.compose.ui.graphics.drawscope.translate
+import androidx.compose.ui.node.DrawModifierNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.invalidateDraw
 import androidx.compose.ui.util.lerp
 import androidx.core.math.MathUtils.clamp
 import com.android.egg.flags.Flags.flagFlag
+import kotlinx.coroutines.DisposableHandle
 import java.lang.Float.max
 import kotlin.math.exp
 import kotlin.math.sqrt
@@ -55,22 +67,108 @@
     ds.scale(zoom) { block(ds) }
 }
 
-class VisibleUniverse(namer: Namer, randomSeed: Long) : Universe(namer, randomSeed) {
-    // Magic variable. Every time we update it, Compose will notice and redraw the universe.
-    val triggerDraw = mutableStateOf(0L)
+/**
+ * A device for observing changes to a [Simulator] such as a [Universe].
+ * [observer] will be invoked each time a [Simulator.step] has completed.
+ */
+@Composable
+fun <S : Simulator> Telescope(
+    subject: S,
+    observer: (S) -> Unit
+) {
+    remember(subject) {
+        object : RememberObserver {
+            lateinit var registration: DisposableHandle
+            var currentObserver by mutableStateOf(observer)
 
-    fun simulateAndDrawFrame(nanos: Long) {
-        // By writing this value, Compose will look for functions that read it (like drawZoomed).
-        triggerDraw.value = nanos
+            override fun onRemembered() {
+                registration = subject.addSimulationStepListener { currentObserver(subject) }
+            }
 
-        step(nanos)
+            override fun onForgotten() {
+                registration.dispose()
+            }
+
+            override fun onAbandoned() {}
+        }
+    }.currentObserver = observer
+}
+
+fun Modifier.drawUniverse(
+    universe: Universe,
+    draw: DrawScope.(Universe) -> Unit
+): Modifier = this then UniverseElement(universe, draw)
+
+@Composable
+fun UniverseCanvas(
+    universe: Universe,
+    modifier: Modifier = Modifier,
+    draw: DrawScope.(Universe) -> Unit
+) {
+    Spacer(modifier.drawUniverse(universe, draw))
+}
+
+private class UniverseElement(
+    val universe: Universe,
+    val draw: DrawScope.(Universe) -> Unit
+) : ModifierNodeElement<UniverseModifierNode>() {
+    override fun create(): UniverseModifierNode = UniverseModifierNode(universe, draw)
+
+    // Called when a modifier is applied to a Layout whose inputs have changed
+    override fun update(node: UniverseModifierNode) {
+        node.universe = universe
+        node.draw = draw
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (javaClass != other?.javaClass) return false
+
+        other as UniverseElement
+
+        if (universe != other.universe) return false
+        if (draw != other.draw) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = universe.hashCode()
+        result = 31 * result + draw.hashCode()
+        return result
     }
 }
 
-fun ZoomedDrawScope.drawUniverse(universe: VisibleUniverse) {
-    with(universe) {
-        triggerDraw.value // Please recompose when this value changes.
+private class UniverseModifierNode(
+    universe: Universe,
+    draw: DrawScope.(Universe) -> Unit,
+) : Modifier.Node(), DrawModifierNode {
+    private val universeListener: () -> Unit = { invalidateDraw() }
+    private var removeUniverseListener: DisposableHandle? =
+        universe.addSimulationStepListener(universeListener)
 
+    var universe: Universe = universe
+        set(value) {
+            if (field === value) return
+            removeUniverseListener?.dispose()
+            field = value
+            removeUniverseListener = value.addSimulationStepListener(universeListener)
+        }
+
+    var draw: ContentDrawScope.(Universe) -> Unit = draw
+        set(value) {
+            if (field === value) return
+            field = value
+            invalidateDraw()
+        }
+
+    override fun ContentDrawScope.draw() {
+        draw(universe)
+    }
+}
+
+fun ZoomedDrawScope.drawUniverse(universe: Universe) {
+    with(universe) {
         constraints.forEach {
             when (it) {
                 is Landing -> drawLanding(it)
diff --git a/packages/SettingsLib/CardPreference/res/drawable/settingslib_card_preference_background.xml b/packages/SettingsLib/CardPreference/res/drawable/settingslib_card_preference_background.xml
new file mode 100644
index 0000000..1d57c16
--- /dev/null
+++ b/packages/SettingsLib/CardPreference/res/drawable/settingslib_card_preference_background.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="?android:colorControlHighlight">
+    <item>
+        <shape android:shape="rectangle">
+            <solid
+                android:color="@color/settingslib_materialColorSecondaryContainer" />
+            <corners
+                android:radius="@dimen/settingslib_expressive_radius_extralarge3" />
+        </shape>
+    </item>
+</ripple>
\ No newline at end of file
diff --git a/packages/SettingsLib/CardPreference/res/layout/settingslib_expressive_preference_card.xml b/packages/SettingsLib/CardPreference/res/layout/settingslib_expressive_preference_card.xml
index 9018bac..4ce106e 100644
--- a/packages/SettingsLib/CardPreference/res/layout/settingslib_expressive_preference_card.xml
+++ b/packages/SettingsLib/CardPreference/res/layout/settingslib_expressive_preference_card.xml
@@ -14,9 +14,13 @@
     See the License for the specific language governing permissions and
     limitations under the License.
 -->
-<com.google.android.material.card.MaterialCardView
+<LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    style="@style/SettingsLibCardStyle">
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingVertical="@dimen/settingslib_expressive_space_extrasmall4">
 
     <LinearLayout
         android:id="@+id/card_container"
@@ -24,10 +28,10 @@
         android:layout_height="wrap_content"
         android:baselineAligned="false"
         android:minHeight="@dimen/settingslib_expressive_space_large3"
-        android:paddingStart="@dimen/settingslib_expressive_space_small1"
-        android:paddingEnd="@dimen/settingslib_expressive_space_small1"
+        android:paddingHorizontal="@dimen/settingslib_expressive_space_medium1"
         android:orientation="horizontal"
-        android:gravity="center_vertical">
+        android:gravity="center_vertical"
+        android:background="@drawable/settingslib_card_preference_background">
 
         <LinearLayout
             android:id="@+id/icon_frame"
@@ -35,15 +39,13 @@
             android:layout_height="wrap_content"
             android:minWidth="@dimen/settingslib_expressive_space_medium3"
             android:minHeight="@dimen/settingslib_expressive_space_medium3"
-            android:gravity="center"
-            android:orientation="horizontal">
-
+            android:gravity="center">
             <ImageView
                 android:id="@android:id/icon"
                 android:layout_width="@dimen/settingslib_expressive_space_medium3"
                 android:layout_height="@dimen/settingslib_expressive_space_medium3"
-                android:scaleType="centerInside"/>
-
+                android:scaleType="centerInside"
+                android:importantForAccessibility="no"/>
         </LinearLayout>
 
         <LinearLayout
@@ -54,19 +56,16 @@
             android:paddingHorizontal="@dimen/settingslib_expressive_space_small1"
             android:paddingVertical="@dimen/settingslib_expressive_space_small2"
             android:orientation="vertical">
-
             <TextView
                 android:id="@android:id/title"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:textAppearance="@style/TextAppearance.CardTitle.SettingsLib"/>
-
+                android:textAppearance="@style/TextAppearance.CardTitle.SettingsLib" />
             <TextView
                 android:id="@android:id/summary"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:textAppearance="@style/TextAppearance.CardSummary.SettingsLib"/>
-
+                android:textAppearance="@style/TextAppearance.CardSummary.SettingsLib" />
         </LinearLayout>
 
         <ImageView
@@ -75,9 +74,9 @@
             android:layout_height="@dimen/settingslib_expressive_space_medium4"
             android:padding="@dimen/settingslib_expressive_space_extrasmall4"
             android:layout_gravity="center"
+            android:contentDescription="@string/settingslib_dismiss_button_content_description"
             android:src="@drawable/settingslib_expressive_icon_close"
-            android:background="?android:attr/selectableItemBackground" />
+            android:tint="@color/settingslib_materialColorOnSecondary" />
 
     </LinearLayout>
-
-</com.google.android.material.card.MaterialCardView>
\ No newline at end of file
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/CardPreference/res/values/styles_expressive.xml b/packages/SettingsLib/CardPreference/res/values/styles_expressive.xml
index 287b13f..e7d4a00 100644
--- a/packages/SettingsLib/CardPreference/res/values/styles_expressive.xml
+++ b/packages/SettingsLib/CardPreference/res/values/styles_expressive.xml
@@ -18,11 +18,11 @@
 <resources>
     <style name="TextAppearance.CardTitle.SettingsLib"
         parent="@style/TextAppearance.SettingsLib.TitleMedium.Emphasized">
-        <item name="android:textColor">@color/settingslib_materialColorOnPrimary</item>
+        <item name="android:textColor">@color/settingslib_materialColorOnSecondaryContainer</item>
     </style>
 
     <style name="TextAppearance.CardSummary.SettingsLib"
         parent="@style/TextAppearance.SettingsLib.LabelMedium">
-        <item name="android:textColor">@color/settingslib_materialColorOnSecondary</item>
+        <item name="android:textColor">@color/settingslib_materialColorOnSecondaryContainer</item>
     </style>
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/Metadata/processor/src/com/android/settingslib/metadata/PreferenceScreenAnnotationProcessor.kt b/packages/SettingsLib/Metadata/processor/src/com/android/settingslib/metadata/PreferenceScreenAnnotationProcessor.kt
index 14e3b87..38b6413 100644
--- a/packages/SettingsLib/Metadata/processor/src/com/android/settingslib/metadata/PreferenceScreenAnnotationProcessor.kt
+++ b/packages/SettingsLib/Metadata/processor/src/com/android/settingslib/metadata/PreferenceScreenAnnotationProcessor.kt
@@ -114,15 +114,7 @@
     private fun generateCode(outputPkg: String, outputClass: String, outputFun: String) {
         // sort by screen keys to make the output deterministic and naturally fit to FixedArrayMap
         screens.sort()
-        val javaFileObject =
-            try {
-                processingEnv.filer.createSourceFile("$outputPkg.$outputClass")
-            } catch (e: Exception) {
-                // quick fix: gradle runs this processor twice unexpectedly
-                warn("cannot createSourceFile: $e")
-                return
-            }
-        javaFileObject.openWriter().use {
+        processingEnv.filer.createSourceFile("$outputPkg.$outputClass").openWriter().use {
             it.write("package $outputPkg;\n\n")
             it.write("import $PACKAGE.FixedArrayMap;\n")
             it.write("import $PACKAGE.FixedArrayMap.OrderedInitializer;\n")
diff --git a/packages/SettingsLib/SettingsTheme/res/values/strings.xml b/packages/SettingsLib/SettingsTheme/res/values/strings.xml
index c36dcb8..f3f077e 100644
--- a/packages/SettingsLib/SettingsTheme/res/values/strings.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values/strings.xml
@@ -21,4 +21,6 @@
     <string name="settingslib_expressive_text_expand">Expand</string>
     <!-- text of button to indicate user the textView is collapsable [CHAR LIMIT=NONE] -->
     <string name="settingslib_expressive_text_collapse">Collapse</string>
+    <!-- Content description of the dismiss button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
+    <string name="settingslib_dismiss_button_content_description">Dismiss</string>
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BytesFormatter.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BytesFormatter.kt
index 5b7e2a8..e6cc8a8 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BytesFormatter.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BytesFormatter.kt
@@ -24,6 +24,7 @@
 import android.icu.text.UnicodeSet
 import android.icu.text.UnicodeSetSpanner
 import android.icu.util.Measure
+import android.text.BidiFormatter
 import android.text.format.Formatter
 import android.text.format.Formatter.RoundedBytesResult
 import java.math.BigDecimal
@@ -40,11 +41,17 @@
     constructor(context: Context) : this(context.resources)
 
     private val locale = resources.configuration.locales[0]
+    private val bidiFormatter = BidiFormatter.getInstance(locale)
 
     fun format(bytes: Long, useCase: UseCase): String {
         val rounded = RoundedBytesResult.roundBytes(bytes, useCase.flag)
         val numberFormatter = getNumberFormatter(rounded.fractionDigits)
-        return numberFormatter.formatRoundedBytesResult(rounded)
+        val formattedString = numberFormatter.formatRoundedBytesResult(rounded)
+        return if (useCase == UseCase.FileSize) {
+            formattedString.bidiWrap()
+        } else {
+            formattedString
+        }
     }
 
     fun formatWithUnits(bytes: Long, useCase: UseCase): Result {
@@ -74,6 +81,14 @@
             }
         }
 
+    /** Wraps the source string in bidi formatting characters in RTL locales. */
+    private fun String.bidiWrap(): String =
+        if (bidiFormatter.isRtlContext) {
+            bidiFormatter.unicodeWrap(this)
+        } else {
+            this
+        }
+
     private companion object {
         fun String.removeFirst(removed: String): String =
             SPACES_AND_CONTROLS.trim(replaceFirst(removed, "")).toString()
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppStorageRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppStorageRepository.kt
new file mode 100644
index 0000000..6fd470c
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppStorageRepository.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.settingslib.spaprivileged.model.app
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.util.Log
+import com.android.settingslib.spaprivileged.framework.common.BytesFormatter
+import com.android.settingslib.spaprivileged.framework.common.storageStatsManager
+
+/** A repository interface for accessing and formatting app storage information. */
+interface AppStorageRepository {
+    /**
+     * Formats the size of an application into a human-readable string.
+     *
+     * This function retrieves the total size of the application, including APK file and its
+     * associated data.
+     *
+     * This function takes an [ApplicationInfo] object as input and returns a formatted string
+     * representing the size of the application. The size is formatted in units like kB, MB, GB,
+     * etc.
+     *
+     * @param app The [ApplicationInfo] object representing the application.
+     * @return A formatted string representing the size of the application.
+     */
+    fun formatSize(app: ApplicationInfo): String
+
+    /**
+     * Formats the size about an application into a human-readable string.
+     *
+     * @param sizeBytes The size in bytes to format.
+     * @return A formatted string representing the size about application.
+     */
+    fun formatSizeBytes(sizeBytes: Long): String
+
+    /**
+     * Calculates the size of an application in bytes.
+     *
+     * This function retrieves the total size of the application, including APK file and its
+     * associated data.
+     *
+     * @param app The [ApplicationInfo] object representing the application.
+     * @return The total size of the application in bytes, or null if the size could not be
+     *   determined.
+     */
+    fun calculateSizeBytes(app: ApplicationInfo): Long?
+}
+
+class AppStorageRepositoryImpl(context: Context) : AppStorageRepository {
+    private val storageStatsManager = context.storageStatsManager
+    private val bytesFormatter = BytesFormatter(context)
+
+    override fun formatSize(app: ApplicationInfo): String {
+        val sizeBytes = calculateSizeBytes(app)
+        return if (sizeBytes != null) formatSizeBytes(sizeBytes) else ""
+    }
+
+    override fun formatSizeBytes(sizeBytes: Long): String =
+        bytesFormatter.format(sizeBytes, BytesFormatter.UseCase.FileSize)
+
+    override fun calculateSizeBytes(app: ApplicationInfo): Long? =
+        try {
+            val stats =
+                storageStatsManager.queryStatsForPackage(
+                    app.storageUuid,
+                    app.packageName,
+                    app.userHandle,
+                )
+            stats.codeBytes + stats.dataBytes
+        } catch (e: Exception) {
+            Log.w(TAG, "Failed to query stats", e)
+            null
+        }
+
+    companion object {
+        private const val TAG = "AppStorageRepository"
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt
index 7a4f81c..7c98e9c 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt
@@ -16,42 +16,30 @@
 
 package com.android.settingslib.spaprivileged.template.app
 
-import android.content.Context
 import android.content.pm.ApplicationInfo
-import android.text.format.Formatter
-import android.util.Log
+import androidx.annotation.VisibleForTesting
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
 import androidx.compose.runtime.remember
-import androidx.compose.ui.platform.LocalContext
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.android.settingslib.spaprivileged.framework.common.storageStatsManager
+import com.android.settingslib.spa.framework.compose.rememberContext
 import com.android.settingslib.spaprivileged.framework.compose.placeholder
-import com.android.settingslib.spaprivileged.model.app.userHandle
+import com.android.settingslib.spaprivileged.model.app.AppStorageRepository
+import com.android.settingslib.spaprivileged.model.app.AppStorageRepositoryImpl
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.flowOn
 
-private const val TAG = "AppStorageSize"
-
 @Composable
-fun ApplicationInfo.getStorageSize(): State<String> {
-    val context = LocalContext.current
-    return remember(this) {
-        flow {
-            val sizeBytes = calculateSizeBytes(context)
-            this.emit(if (sizeBytes != null) Formatter.formatFileSize(context, sizeBytes) else "")
-        }.flowOn(Dispatchers.IO)
-    }.collectAsStateWithLifecycle(initialValue = placeholder())
-}
+fun ApplicationInfo.getStorageSize(): State<String> =
+    getStorageSize(rememberContext(::AppStorageRepositoryImpl))
 
-fun ApplicationInfo.calculateSizeBytes(context: Context): Long? {
-    val storageStatsManager = context.storageStatsManager
-    return try {
-        val stats = storageStatsManager.queryStatsForPackage(storageUuid, packageName, userHandle)
-        stats.codeBytes + stats.dataBytes
-    } catch (e: Exception) {
-        Log.w(TAG, "Failed to query stats: $e")
-        null
-    }
+@VisibleForTesting
+@Composable
+fun ApplicationInfo.getStorageSize(appStorageRepository: AppStorageRepository): State<String> {
+    val app = this
+    return remember(app) {
+            flow { emit(appStorageRepository.formatSize(app)) }.flowOn(Dispatchers.Default)
+        }
+        .collectAsStateWithLifecycle(initialValue = placeholder())
 }
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppStorageRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppStorageRepositoryTest.kt
new file mode 100644
index 0000000..e8ec974b
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppStorageRepositoryTest.kt
@@ -0,0 +1,94 @@
+/*
+ * 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.settingslib.spaprivileged.model.app
+
+import android.app.usage.StorageStats
+import android.app.usage.StorageStatsManager
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager.NameNotFoundException
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spaprivileged.framework.common.storageStatsManager
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.doThrow
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.stub
+import java.util.UUID
+
+@RunWith(AndroidJUnit4::class)
+class AppStorageRepositoryTest {
+    private val app = ApplicationInfo().apply { storageUuid = UUID.randomUUID() }
+
+    private val mockStorageStatsManager =
+        mock<StorageStatsManager> {
+            on { queryStatsForPackage(app.storageUuid, app.packageName, app.userHandle) } doReturn
+                STATS
+        }
+
+    private val context: Context =
+        spy(ApplicationProvider.getApplicationContext()) {
+            on { storageStatsManager } doReturn mockStorageStatsManager
+        }
+
+    private val repository = AppStorageRepositoryImpl(context)
+
+    @Test
+    fun calculateSizeBytes() {
+        val sizeBytes = repository.calculateSizeBytes(app)
+
+        assertThat(sizeBytes).isEqualTo(120)
+    }
+
+    @Test
+    fun formatSize() {
+        val fileSize = repository.formatSize(app)
+
+        assertThat(fileSize).isEqualTo("120 byte")
+    }
+
+    @Test
+    fun formatSize_throwException() {
+        mockStorageStatsManager.stub {
+            on { queryStatsForPackage(app.storageUuid, app.packageName, app.userHandle) } doThrow
+                NameNotFoundException()
+        }
+
+        val fileSize = repository.formatSize(app)
+
+        assertThat(fileSize).isEqualTo("")
+    }
+
+    @Test
+    fun formatSizeBytes() {
+        val fileSize = repository.formatSizeBytes(120)
+
+        assertThat(fileSize).isEqualTo("120 byte")
+    }
+
+    companion object {
+        private val STATS =
+            StorageStats().apply {
+                codeBytes = 100
+                dataBytes = 20
+            }
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
index 60f3d0c..4f42c82 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
@@ -16,98 +16,37 @@
 
 package com.android.settingslib.spaprivileged.template.app
 
-import android.app.usage.StorageStats
-import android.app.usage.StorageStatsManager
-import android.content.Context
 import android.content.pm.ApplicationInfo
-import android.content.pm.PackageManager.NameNotFoundException
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.settingslib.spa.framework.compose.stateOf
-import com.android.settingslib.spaprivileged.framework.common.storageStatsManager
-import com.android.settingslib.spaprivileged.model.app.userHandle
-import java.util.UUID
-import org.junit.Before
+import com.android.settingslib.spaprivileged.model.app.AppStorageRepository
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Spy
-import org.mockito.junit.MockitoJUnit
-import org.mockito.junit.MockitoRule
-import org.mockito.kotlin.whenever
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import java.util.UUID
 
 @RunWith(AndroidJUnit4::class)
 class AppStorageSizeTest {
-    @get:Rule
-    val mockito: MockitoRule = MockitoJUnit.rule()
+    @get:Rule val composeTestRule = createComposeRule()
 
-    @get:Rule
-    val composeTestRule = createComposeRule()
+    private val app = ApplicationInfo().apply { storageUuid = UUID.randomUUID() }
 
-    @Spy
-    private val context: Context = ApplicationProvider.getApplicationContext()
-
-    @Mock
-    private lateinit var storageStatsManager: StorageStatsManager
-
-    private val app = ApplicationInfo().apply {
-        storageUuid = UUID.randomUUID()
-    }
-
-    @Before
-    fun setUp() {
-        whenever(context.storageStatsManager).thenReturn(storageStatsManager)
-        whenever(
-            storageStatsManager.queryStatsForPackage(
-                app.storageUuid,
-                app.packageName,
-                app.userHandle,
-            )
-        ).thenReturn(STATS)
-    }
+    private val mockAppStorageRepository =
+        mock<AppStorageRepository> { on { formatSize(app) } doReturn SIZE }
 
     @Test
     fun getStorageSize() {
         var storageSize = stateOf("")
 
-        composeTestRule.setContent {
-            CompositionLocalProvider(LocalContext provides context) {
-                storageSize = app.getStorageSize()
-            }
-        }
+        composeTestRule.setContent { storageSize = app.getStorageSize(mockAppStorageRepository) }
 
-        composeTestRule.waitUntil { storageSize.value == "120 B" }
+        composeTestRule.waitUntil { storageSize.value == SIZE }
     }
 
-    @Test
-    fun getStorageSize_throwException() {
-        var storageSize = stateOf("Computing")
-        whenever(
-            storageStatsManager.queryStatsForPackage(
-                app.storageUuid,
-                app.packageName,
-                app.userHandle,
-            )
-        ).thenThrow(NameNotFoundException())
-
-        composeTestRule.setContent {
-            CompositionLocalProvider(LocalContext provides context) {
-                storageSize = app.getStorageSize()
-            }
-        }
-
-        composeTestRule.waitUntil { storageSize.value == "" }
-    }
-
-    companion object {
-        private val STATS = StorageStats().apply {
-            codeBytes = 100
-            dataBytes = 20
-            cacheBytes = 3
-        }
+    private companion object {
+        const val SIZE = "120 kB"
     }
 }
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index bbe08f2..d94450b 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -219,3 +219,13 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "adopt_primary_group_management_api"
+    namespace: "cross_device_experiences"
+    description: "Adopt Bluetooth LE broadcast primary group management APIs"
+    bug: "381946931"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java b/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java
index fa43915..5b25d0d 100644
--- a/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java
+++ b/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java
@@ -35,7 +35,6 @@
 import javax.annotation.processing.RoundEnvironment;
 import javax.annotation.processing.SupportedAnnotationTypes;
 import javax.annotation.processing.SupportedOptions;
-import javax.annotation.processing.SupportedSourceVersion;
 import javax.lang.model.SourceVersion;
 import javax.lang.model.element.Element;
 import javax.lang.model.element.Modifier;
@@ -48,7 +47,6 @@
  * Annotation processor for {@link SearchIndexable} that generates {@link SearchIndexableResources}
  * subclasses.
  */
-@SupportedSourceVersion(SourceVersion.RELEASE_17)
 @SupportedOptions(IndexableProcessor.PACKAGE_KEY)
 @SupportedAnnotationTypes({"com.android.settingslib.search.SearchIndexable"})
 public class IndexableProcessor extends AbstractProcessor {
@@ -69,6 +67,11 @@
     private boolean mRanOnce;
 
     @Override
+    public SourceVersion getSupportedSourceVersion() {
+        return SourceVersion.latestSupported();
+    }
+
+    @Override
     public boolean process(Set<? extends TypeElement> annotations,
             RoundEnvironment roundEnvironment) {
         if (mRanOnce) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 1998d0c..2b8e20f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -10,6 +10,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
 import android.content.pm.Signature;
 import android.content.pm.UserInfo;
 import android.content.res.ColorStateList;
@@ -32,6 +33,7 @@
 import android.net.ConnectivityManager;
 import android.net.NetworkCapabilities;
 import android.net.TetheringManager;
+import android.net.Uri;
 import android.net.vcn.VcnUtils;
 import android.net.wifi.WifiInfo;
 import android.os.BatteryManager;
@@ -79,11 +81,14 @@
     @VisibleForTesting
     static final String STORAGE_MANAGER_ENABLED_PROPERTY = "ro.storage_manager.enabled";
 
+    private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive";
+
     private static Signature[] sSystemSignature;
     private static String sPermissionControllerPackageName;
     private static String sServicesSystemSharedLibPackageName;
     private static String sSharedSystemSharedLibPackageName;
     private static String sDefaultWebViewPackageName;
+    private static String sPackageInstallerPackageName;
 
     static final int[] WIFI_PIE = {
         com.android.internal.R.drawable.ic_wifi_signal_0,
@@ -496,9 +501,29 @@
                 || packageName.equals(sSharedSystemSharedLibPackageName)
                 || packageName.equals(PrintManager.PRINT_SPOOLER_PACKAGE_NAME)
                 || packageName.equals(getDefaultWebViewPackageName(pm))
+                || packageName.equals(getPackageInstallerPackageName(pm))
                 || isDeviceProvisioningPackage(resources, packageName);
     }
 
+    /** Return the package name of the installer */
+    private static String getPackageInstallerPackageName(PackageManager pm) {
+        if (sPackageInstallerPackageName != null) {
+            return sPackageInstallerPackageName;
+        }
+        final Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
+        intent.addCategory(Intent.CATEGORY_DEFAULT);
+        intent.setDataAndType(Uri.parse("content://com.example/foo.apk"), PACKAGE_MIME_TYPE);
+        final List<ResolveInfo> matches =
+                pm.queryIntentActivities(intent, PackageManager.GET_META_DATA);
+        if (matches.size() == 1) {
+            final ResolveInfo resolveInfo = matches.get(0);
+            if (resolveInfo.activityInfo.applicationInfo.isPrivilegedApp()) {
+                sPackageInstallerPackageName = resolveInfo.getComponentInfo().packageName;
+            }
+        }
+        return sPackageInstallerPackageName;
+    }
+
     /**
      * Returns {@code true} if the supplied package is the device provisioning app. Otherwise,
      * returns {@code false}.
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
index 4b7cb36..bf86911 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
@@ -134,6 +134,8 @@
             // Do nothing if GroupId has been assigned
             if (!isValidGroupId(cachedDevice.getGroupId())) {
                 final int newGroupId = getBaseGroupId(cachedDevice.getDevice());
+                log("updateCsipDevices: propose new group id " + newGroupId + " for device "
+                        + cachedDevice.getDevice());
                 // Do nothing if there is no GroupId on Bluetooth device
                 if (isValidGroupId(newGroupId)) {
                     cachedDevice.setGroupId(newGroupId);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index 7c24df9..ff5e9e6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -358,6 +358,9 @@
                     && mProfile instanceof CsipSetCoordinatorProfile;
 
             if (isAshaProfile && (newState == BluetoothProfile.STATE_CONNECTED)) {
+                if (DEBUG) {
+                    Log.d(TAG, "onReceive, hearing aid profile connected, check hisyncid");
+                }
                 // Check if the HiSyncID has being initialized
                 if (cachedDevice.getHiSyncId() == BluetoothHearingAid.HI_SYNC_ID_INVALID) {
                     long newHiSyncId = getHearingAidProfile().getHiSyncId(cachedDevice.getDevice());
@@ -375,7 +378,9 @@
             }
 
             if (isHapClientOrLeAudioProfile && newState == BluetoothProfile.STATE_CONNECTED) {
-
+                if (DEBUG) {
+                    Log.d(TAG, "onReceive, hap/lea profile connected, check hearing aid info");
+                }
                 // Checks if both profiles are connected to the device. Hearing aid info need
                 // to be retrieved from these profiles separately.
                 if (cachedDevice.isConnectedLeAudioHearingAidDevice()) {
@@ -389,10 +394,16 @@
             }
 
             if (isCsipProfile && (newState == BluetoothProfile.STATE_CONNECTED)) {
+                if (DEBUG) {
+                    Log.d(TAG, "onReceive, csip profile connected, check group id");
+                }
                 // Check if the GroupID has being initialized
                 if (cachedDevice.getGroupId() == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
                     final Map<Integer, ParcelUuid> groupIdMap = getCsipSetCoordinatorProfile()
                             .getGroupUuidMapByDevice(cachedDevice.getDevice());
+                    if (DEBUG) {
+                        Log.d(TAG, "csip group uuid map = " + groupIdMap);
+                    }
                     if (groupIdMap != null) {
                         for (Map.Entry<Integer, ParcelUuid> entry: groupIdMap.entrySet()) {
                             if (entry.getValue().equals(BluetoothUuid.CAP)) {
@@ -431,6 +442,9 @@
                         mProfile.getProfileId());
             }
             if (needDispatchProfileConnectionState) {
+                if (DEBUG) {
+                    Log.d(TAG, "needDispatchProfileConnectionState");
+                }
                 cachedDevice.refresh();
                 mEventManager.dispatchProfileConnectionStateChanged(cachedDevice, newState,
                         mProfile.getProfileId());
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
index 84afb9f..a1cf409 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
@@ -37,10 +37,10 @@
 
     private static final String TAG = "AbstractPrefController";
 
-    protected final Context mContext;
+    protected final @NonNull Context mContext;
     private final DevicePolicyManager mDevicePolicyManager;
 
-    public AbstractPreferenceController(Context context) {
+    public AbstractPreferenceController(@NonNull Context context) {
         mContext = context;
         mDevicePolicyManager =
                 (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
index 9aaefe47..58c7907 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
@@ -37,8 +37,9 @@
     override val globalZenMode: StateFlow<Int>
         get() = mutableZenMode.asStateFlow()
 
-    private val mutableModesFlow: MutableStateFlow<List<ZenMode>> =
+    private val mutableModesFlow: MutableStateFlow<List<ZenMode>> by lazy {
         MutableStateFlow(listOf(TestModeBuilder.MANUAL_DND))
+    }
     override val modes: Flow<List<ZenMode>>
         get() = mutableModesFlow.asStateFlow()
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index cb656bd..9b75a47 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -1869,7 +1869,7 @@
                 }
                 case MUTATION_OPERATION_RESET -> {
                     return mSettingsRegistry.resetSettingsLocked(SETTINGS_TYPE_SECURE,
-                            UserHandle.USER_SYSTEM, callingPackage, mode, tag);
+                            owningUserId, callingPackage, mode, tag);
                 }
             }
         }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index bf3afed..0f63115 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -408,79 +408,10 @@
                         Slog.w(LOG_TAG, "Bulk sync request to acongid failed.");
                     }
                 }
-
-                if (Flags.disableBulkCompare()) {
-                    return;
-                }
-
-                // TOBO(b/312444587): remove the comparison logic after Test Mission 2.
-                if (requests == null) {
-                    Map<String, AconfigdFlagInfo> aconfigdFlagMap =
-                            AconfigdJavaUtils.listFlagsValueInNewStorage(localSocket);
-                    compareFlagValueInNewStorage(
-                            mAconfigDefaultFlags,
-                            aconfigdFlagMap);
-                }
             }
         }
     }
 
-    // TODO(b/312444587): remove the comparison logic after Test Mission 2.
-    public int compareFlagValueInNewStorage(
-            Map<String, AconfigdFlagInfo> defaultFlagMap,
-            Map<String, AconfigdFlagInfo> aconfigdFlagMap) {
-
-        // Get all defaults from the default map. The mSettings may not contain
-        // all flags, since it only contains updated flags.
-        int diffNum = 0;
-        for (Map.Entry<String, AconfigdFlagInfo> entry : defaultFlagMap.entrySet()) {
-            String key = entry.getKey();
-            AconfigdFlagInfo flag = entry.getValue();
-
-            AconfigdFlagInfo aconfigdFlag = aconfigdFlagMap.get(key);
-            if (aconfigdFlag == null) {
-                Slog.w(LOG_TAG, String.format("Flag %s is missing from aconfigd", key));
-                diffNum++;
-                continue;
-            }
-            String diff = flag.dumpDiff(aconfigdFlag);
-            if (!diff.isEmpty()) {
-                Slog.w(
-                        LOG_TAG,
-                        String.format(
-                                "Flag %s is different in Settings and aconfig: %s", key, diff));
-                diffNum++;
-            }
-        }
-
-        for (String key : aconfigdFlagMap.keySet()) {
-            if (defaultFlagMap.containsKey(key)) continue;
-            Slog.w(LOG_TAG, String.format("Flag %s is missing from Settings", key));
-            diffNum++;
-        }
-
-        String compareMarkerName = "aconfigd_marker/compare_diff_num";
-        synchronized (mLock) {
-            Setting markerSetting = mSettings.get(compareMarkerName);
-            if (markerSetting == null) {
-                markerSetting =
-                        new Setting(
-                                compareMarkerName,
-                                String.valueOf(diffNum),
-                                false,
-                                "aconfig",
-                                "aconfig");
-                mSettings.put(compareMarkerName, markerSetting);
-            }
-            markerSetting.value = String.valueOf(diffNum);
-        }
-
-        if (diffNum == 0) {
-            Slog.w(LOG_TAG, "Settings and new storage have same flags.");
-        }
-        return diffNum;
-    }
-
     @GuardedBy("mLock")
     public int getAllAconfigFlagsFromSettings(
             @NonNull Map<String, AconfigdFlagInfo> flagInfoDefault) {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
index cfd27c6..4fc3b87 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
+++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
@@ -100,14 +100,4 @@
     metadata {
         purpose: PURPOSE_BUGFIX
     }
-}
-
-flag {
-    name: "disable_bulk_compare"
-    namespace: "core_experiments_team_internal"
-    description: "Disable bulk comparison between DeviceConfig and aconfig storage."
-    bug: "312444587"
-    metadata {
-        purpose: PURPOSE_BUGFIX
-    }
 }
\ No newline at end of file
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index 276b206..6e75766 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -1303,85 +1303,4 @@
         assertFalse(flag3.getHasServerOverride());
         assertFalse(flag3.getHasLocalOverride());
     }
-
-    @Test
-    @RequiresFlagsDisabled(Flags.FLAG_DISABLE_BULK_COMPARE)
-    public void testCompareFlagValueInNewStorage() {
-                int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
-        Object lock = new Object();
-        SettingsState settingsState =
-                new SettingsState(
-                        InstrumentationRegistry.getContext(),
-                        lock,
-                        mSettingsFile,
-                        configKey,
-                        SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED,
-                        Looper.getMainLooper());
-
-        AconfigdFlagInfo defaultFlag1 =
-                AconfigdFlagInfo.newBuilder()
-                        .setPackageName("com.android.flags")
-                        .setFlagName("flag1")
-                        .setDefaultFlagValue("false")
-                        .setServerFlagValue("true")
-                        .setHasServerOverride(true)
-                        .setIsReadWrite(true)
-                        .build();
-
-        AconfigdFlagInfo expectedFlag1 =
-                AconfigdFlagInfo.newBuilder()
-                        .setPackageName("com.android.flags")
-                        .setFlagName("flag1")
-                        .setServerFlagValue("true")
-                        .setDefaultFlagValue("false")
-                        .setHasServerOverride(true)
-                        .setIsReadWrite(true)
-                        .build();
-
-        Map<String, AconfigdFlagInfo> aconfigdMap = new HashMap<>();
-        Map<String, AconfigdFlagInfo> defaultMap = new HashMap<>();
-
-        defaultMap.put("com.android.flags.flag1", defaultFlag1);
-        aconfigdMap.put("com.android.flags.flag1", expectedFlag1);
-
-        int ret = settingsState.compareFlagValueInNewStorage(defaultMap, aconfigdMap);
-        assertEquals(0, ret);
-
-        String value =
-                settingsState.getSettingLocked("aconfigd_marker/compare_diff_num").getValue();
-        assertEquals("0", value);
-
-        AconfigdFlagInfo defaultFlag2 =
-                AconfigdFlagInfo.newBuilder()
-                        .setPackageName("com.android.flags")
-                        .setFlagName("flag2")
-                        .setDefaultFlagValue("false")
-                        .build();
-        defaultMap.put("com.android.flags.flag2", defaultFlag2);
-
-        ret = settingsState.compareFlagValueInNewStorage(defaultMap, aconfigdMap);
-        // missing from new storage
-        assertEquals(1, ret);
-        value =
-                settingsState.getSettingLocked("aconfigd_marker/compare_diff_num").getValue();
-        assertEquals("1", value);
-
-        AconfigdFlagInfo expectedFlag2 =
-        AconfigdFlagInfo.newBuilder()
-                .setPackageName("com.android.flags")
-                .setFlagName("flag2")
-                .setServerFlagValue("true")
-                .setLocalFlagValue("true")
-                .setDefaultFlagValue("false")
-                .setHasServerOverride(true)
-                .setHasLocalOverride(true)
-                .build();
-        aconfigdMap.put("com.android.flags.flag2", expectedFlag2);
-        ret = settingsState.compareFlagValueInNewStorage(defaultMap, aconfigdMap);
-        // skip the server and local value comparison when the flag is read_only
-        assertEquals(0, ret);
-        value =
-                settingsState.getSettingLocked("aconfigd_marker/compare_diff_num").getValue();
-        assertEquals("0", value);
-    }
 }
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 6b2449f..a935aac 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -423,6 +423,7 @@
     ],
     manifest: "AndroidManifest-res.xml",
     flags_packages: [
+        "android.app.flags-aconfig",
         "com_android_systemui_flags",
     ],
 }
@@ -519,6 +520,7 @@
         "androidx.activity_activity-compose",
         "androidx.compose.animation_animation-graphics",
         "androidx.lifecycle_lifecycle-viewmodel-compose",
+        "kairos",
     ],
     libs: [
         "keepanno-annotations",
@@ -739,6 +741,7 @@
         "PlatformMotionTesting",
         "SystemUICustomizationTestUtils",
         "androidx.compose.runtime_runtime",
+        "kairos",
         "kosmos",
         "testables",
         "androidx.test.rules",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 51ea529..b53198d 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -406,7 +406,7 @@
         android:killAfterRestore="false"
         android:hardwareAccelerated="true"
         android:label="@string/app_label"
-        android:icon="@drawable/android15_patch_adaptive"
+        android:icon="@drawable/android16_patch_adaptive"
         android:process="com.android.systemui"
         android:supportsRtl="true"
         android:theme="@style/Theme.SystemUI"
@@ -547,6 +547,12 @@
                 android:permission="android.permission.BIND_WALLPAPER"
                 android:exported="true" />
 
+        <service android:name=".wallpapers.GradientColorWallpaper"
+            android:featureFlag="android.app.enable_connected_displays_wallpaper"
+            android:singleUser="true"
+            android:permission="android.permission.BIND_WALLPAPER"
+            android:exported="true" />
+
         <activity android:name=".tuner.TunerActivity"
                   android:enabled="false"
                   android:icon="@drawable/tuner"
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
index 0f210e7..b40a114 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
@@ -20,7 +20,10 @@
     <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS"/>
     <uses-permission android:name="android.permission.MANAGE_USERS"/>
 
-    <application android:supportsRtl="true">
+    <application
+        android:supportsRtl="true"
+        android:allowBackup="true"
+        android:restoreAnyVersion="true">
         <service
             android:name="com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService"
             android:exported="false"
diff --git a/packages/SystemUI/aconfig/Android.bp b/packages/SystemUI/aconfig/Android.bp
index 1858c80..088ec13 100644
--- a/packages/SystemUI/aconfig/Android.bp
+++ b/packages/SystemUI/aconfig/Android.bp
@@ -23,6 +23,7 @@
     default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
     default_visibility: [
         "//visibility:override",
+        "//frameworks/base/libs/WindowManager/Shell:__subpackages__",
         "//frameworks/base/packages/SystemUI:__subpackages__",
         "//frameworks/libs/systemui/tracinglib:__subpackages__",
         "//frameworks/base/services/accessibility:__subpackages__",
diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig
index b5eba08..fb21be4 100644
--- a/packages/SystemUI/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/aconfig/accessibility.aconfig
@@ -38,6 +38,16 @@
 }
 
 flag {
+    name: "floating_menu_display_cutout_support"
+    namespace: "accessibility"
+    description: "Makes FAB properly react to and avoid DisplayCutouts."
+    bug: "384399408"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "floating_menu_drag_to_hide"
     namespace: "accessibility"
     description: "Allows users to hide the FAB then use notification to dismiss or bring it back."
@@ -125,3 +135,13 @@
     description: "Update hearing device icon in floating menu according to the connection status."
     bug: "357882387"
 }
+
+flag {
+    name: "floating_menu_notify_targets_changed_on_strict_diff"
+    namespace: "accessibility"
+    description: "Only notify listeners that the list of accessibility targets has changed if the lists are not identical."
+    bug: "376473165"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index a0d7fd2..b33421d 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -382,6 +382,13 @@
 }
 
 flag {
+    name: "status_bar_mobile_icon_kairos"
+    namespace: "systemui"
+    description: "Refactors the mobile connection icon in the status bar to use the Kairos library"
+    bug: "383172066"
+}
+
+flag {
     name: "status_bar_monochrome_icons_fix"
     namespace: "systemui"
     description: "Fixes the status bar icon size when drawing InsetDrawables (ie. monochrome icons)"
@@ -1507,16 +1514,6 @@
 }
 
 flag {
-   name: "sim_pin_talkback_fix_for_double_submit"
-   namespace: "systemui"
-   description: "The SIM PIN entry screens show the wrong message due"
-   bug: "346932439"
-   metadata {
-        purpose: PURPOSE_BUGFIX
-   }
-}
-
-flag {
    name: "sim_pin_bouncer_reset"
    namespace: "systemui"
    description: "The SIM PIN bouncer does not close after unlocking"
@@ -1912,3 +1909,13 @@
    description: "Special UI treatment for magic actions"
    bug: "383567383"
 }
+
+flag {
+    name: "show_audio_sharing_slider_in_volume_panel"
+    namespace: "cross_device_experiences"
+    description: "Show two sliders in volume panel when audio sharing."
+    bug: "336183611"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java
index 2e8f928..4b86108 100644
--- a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java
+++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java
@@ -185,16 +185,24 @@
             return;
         }
         ViewGroup.LayoutParams params = mView.getLayoutParams();
-        if (params == null || params.width == 0 || params.height == 0) {
+        if (params == null) {
             // layout pass didn't happen.
             logD("draw: skipped - no layout");
             return;
         }
+
+        final Rect realBounds = getRealBounds();
+        if (realBounds.width() == 0 || realBounds.height() == 0) {
+            // bad bounds.
+            logD("draw: skipped - zero bounds");
+            return;
+        }
+
+
         Canvas canvas = mSurface.lockHardwareCanvas();
         // Clear the canvas first.
         canvas.drawColor(0, PorterDuff.Mode.CLEAR);
         if (mVisibleOverride) {
-            Rect realBounds = getRealBounds();
             Rect renderBounds = getBounds();
             canvas.translate(renderBounds.left, renderBounds.top);
             canvas.scale(
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInContainer.kt b/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInContainer.kt
index 3115191..d08d859 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInContainer.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInContainer.kt
@@ -19,9 +19,11 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableFloatStateOf
 import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.drawWithContent
+import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Path
 import androidx.compose.ui.graphics.drawscope.ContentDrawScope
 import androidx.compose.ui.graphics.drawscope.DrawScope
@@ -30,14 +32,14 @@
 import androidx.compose.ui.graphics.layer.GraphicsLayer
 import androidx.compose.ui.graphics.layer.drawLayer
 import androidx.compose.ui.layout.LayoutCoordinates
-import androidx.compose.ui.layout.layout
+import androidx.compose.ui.layout.onPlaced
 import androidx.compose.ui.layout.positionInWindow
 import androidx.compose.ui.modifier.ModifierLocalModifierNode
 import androidx.compose.ui.node.DrawModifierNode
+import androidx.compose.ui.node.LayoutAwareModifierNode
 import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.node.requireDensity
 import androidx.compose.ui.node.requireGraphicsContext
-import androidx.compose.ui.node.requireLayoutCoordinates
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.util.fastForEach
@@ -48,17 +50,7 @@
  * The elements redirected to this container will be drawn above the content of this composable.
  */
 fun Modifier.container(state: ContainerState): Modifier {
-    return layout { measurable, constraints ->
-            val p = measurable.measure(constraints)
-            layout(p.width, p.height) {
-                val coords = coordinates
-                if (coords != null && !isLookingAhead) {
-                    state.lastCoords = coords
-                }
-
-                p.place(0, 0)
-            }
-        }
+    return onPlaced { state.lastOffsetInWindow = it.positionInWindow() }
         .drawWithContent {
             drawContent()
             state.drawInOverlay(this)
@@ -91,7 +83,7 @@
 
 class ContainerState {
     private var renderers = mutableStateListOf<LayerRenderer>()
-    internal var lastCoords: LayoutCoordinates? = null
+    internal var lastOffsetInWindow by mutableStateOf(Offset.Zero)
 
     internal fun onLayerRendererAttached(renderer: LayerRenderer) {
         renderers.add(renderer)
@@ -142,7 +134,8 @@
     var enabled: () -> Boolean = { true },
     zIndex: Float = 0f,
     var clipPath: (LayoutDirection, Density) -> Path? = { _, _ -> null },
-) : Modifier.Node(), DrawModifierNode, ModifierLocalModifierNode {
+) : Modifier.Node(), LayoutAwareModifierNode, DrawModifierNode, ModifierLocalModifierNode {
+    private var lastOffsetInWindow by mutableStateOf(Offset.Zero)
     var zIndex by mutableFloatStateOf(zIndex)
 
     private inner class LayerWithRenderer(val layer: GraphicsLayer) : LayerRenderer {
@@ -152,11 +145,7 @@
         override fun drawInOverlay(drawScope: DrawScope) {
             if (enabled()) {
                 with(drawScope) {
-                    val containerCoords =
-                        checkNotNull(state.lastCoords) { "container is not placed" }
-                    val (x, y) =
-                        requireLayoutCoordinates().positionInWindow() -
-                            containerCoords.positionInWindow()
+                    val (x, y) = lastOffsetInWindow - state.lastOffsetInWindow
                     val clipPath = clipPath(layoutDirection, requireDensity())
                     if (clipPath != null) {
                         clipPath(clipPath) { translate(x, y) { drawLayer(layer) } }
@@ -178,6 +167,10 @@
         }
     }
 
+    override fun onPlaced(coordinates: LayoutCoordinates) {
+        lastOffsetInWindow = coordinates.positionInWindow()
+    }
+
     val layer: GraphicsLayer?
         get() = layerWithRenderer?.layer
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
index 0054a4c8..4399685 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
@@ -168,7 +168,7 @@
         LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Expanded
 
     FoldAware(
-        modifier = modifier.padding(start = 32.dp, top = 92.dp, end = 32.dp, bottom = 48.dp),
+        modifier = modifier.padding(top = 92.dp, bottom = 48.dp),
         viewModel = viewModel,
         aboveFold = {
             Column(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
index 8321238..3d0354a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
@@ -24,6 +24,8 @@
 import androidx.compose.foundation.gestures.awaitEachGesture
 import androidx.compose.foundation.gestures.awaitFirstDown
 import androidx.compose.foundation.gestures.detectDragGestures
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.width
 import androidx.compose.material3.MaterialTheme
@@ -35,6 +37,7 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clipToBounds
 import androidx.compose.ui.geometry.Offset
@@ -45,6 +48,7 @@
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalView
 import androidx.compose.ui.res.integerResource
+import androidx.compose.ui.unit.DpSize
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.Easings
@@ -212,23 +216,27 @@
     var gridCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) }
     var offset: Offset by remember { mutableStateOf(Offset.Zero) }
     var scale: Float by remember { mutableStateOf(1f) }
+    // This is the size of the drawing area, in dips.
+    val dotDrawingArea =
+        remember(colCount, rowCount) {
+            DpSize(
+                // Because the width also includes spacing to the left and right of the leftmost and
+                // rightmost dots in the grid and because UX mocks specify the width without that
+                // spacing, the actual width needs to be defined slightly bigger than the UX mock
+                // width.
+                width = (262 * colCount / 2).dp,
+                // Because the height also includes spacing above and below the topmost and
+                // bottommost
+                // dots in the grid and because UX mocks specify the height without that spacing,
+                // the
+                // actual height needs to be defined slightly bigger than the UX mock height.
+                height = (262 * rowCount / 2).dp,
+            )
+        }
 
-    Canvas(
-        modifier
-            .sysuiResTag("bouncer_pattern_root")
-            // Because the width also includes spacing to the left and right of the leftmost and
-            // rightmost dots in the grid and because UX mocks specify the width without that
-            // spacing, the actual width needs to be defined slightly bigger than the UX mock width.
-            .width((262 * colCount / 2).dp)
-            // Because the height also includes spacing above and below the topmost and bottommost
-            // dots in the grid and because UX mocks specify the height without that spacing, the
-            // actual height needs to be defined slightly bigger than the UX mock height.
-            .height((262 * rowCount / 2).dp)
-            // Need to clip to bounds to make sure that the lines don't follow the input pointer
-            // when it leaves the bounds of the dot grid.
-            .clipToBounds()
-            .onGloballyPositioned { coordinates -> gridCoordinates = coordinates }
-            .thenIf(isInputEnabled) {
+    Box(
+        modifier =
+            modifier.fillMaxWidth().thenIf(isInputEnabled) {
                 Modifier.pointerInput(Unit) {
                         awaitEachGesture {
                             awaitFirstDown()
@@ -257,106 +265,126 @@
                             inputPosition = change.position
                             change.position.minus(offset).div(scale).let {
                                 viewModel.onDrag(
-                                    xPx = it.x,
+                                    xPx =
+                                        it.x -
+                                            ((size.width - dotDrawingArea.width.roundToPx()) / 2),
                                     yPx = it.y,
-                                    containerSizePx = size.width,
+                                    containerSizePx = dotDrawingArea.width.roundToPx(),
                                 )
                             }
                         }
                     }
             }
-            .motionTestValues {
-                entryAnimationCompleted exportAs entryCompleted
-                dotAppearFadeInAnimatables.map { it.value.value } exportAs dotAppearFadeIn
-                dotAppearMoveUpAnimatables.map { it.value.value } exportAs dotAppearMoveUp
-                dotScalingAnimatables.map { it.value.value } exportAs dotScaling
-            }
     ) {
-        gridCoordinates?.let { nonNullCoordinates ->
-            val containerSize = nonNullCoordinates.size
-            if (containerSize.width <= 0 || containerSize.height <= 0) {
-                return@let
-            }
+        Canvas(
+            Modifier.sysuiResTag("bouncer_pattern_root")
+                .width(dotDrawingArea.width)
+                .height(dotDrawingArea.height)
+                // Need to clip to bounds to make sure that the lines don't follow the input pointer
+                // when it leaves the bounds of the dot grid.
+                .clipToBounds()
+                .align(Alignment.Center)
+                .onGloballyPositioned { coordinates -> gridCoordinates = coordinates }
+                .motionTestValues {
+                    entryAnimationCompleted exportAs entryCompleted
+                    dotAppearFadeInAnimatables.map { it.value.value } exportAs dotAppearFadeIn
+                    dotAppearMoveUpAnimatables.map { it.value.value } exportAs dotAppearMoveUp
+                    dotScalingAnimatables.map { it.value.value } exportAs dotScaling
+                }
+        ) {
+            gridCoordinates?.let { nonNullCoordinates ->
+                val containerSize = nonNullCoordinates.size
+                if (containerSize.width <= 0 || containerSize.height <= 0) {
+                    return@let
+                }
 
-            val horizontalSpacing = containerSize.width.toFloat() / colCount
-            val verticalSpacing = containerSize.height.toFloat() / rowCount
-            val spacing = min(horizontalSpacing, verticalSpacing)
-            val horizontalOffset =
-                offset(
-                    availableSize = containerSize.width,
-                    spacingPerDot = spacing,
-                    dotCount = colCount,
-                    isCentered = true,
-                )
-            val verticalOffset =
-                offset(
-                    availableSize = containerSize.height,
-                    spacingPerDot = spacing,
-                    dotCount = rowCount,
-                    isCentered = centerDotsVertically,
-                )
-            offset = Offset(horizontalOffset, verticalOffset)
-            scale = (colCount * spacing) / containerSize.width
+                val horizontalSpacing = containerSize.width.toFloat() / colCount
+                val verticalSpacing = containerSize.height.toFloat() / rowCount
+                val spacing = min(horizontalSpacing, verticalSpacing)
+                val horizontalOffset =
+                    offset(
+                        availableSize = containerSize.width,
+                        spacingPerDot = spacing,
+                        dotCount = colCount,
+                        isCentered = true,
+                    )
+                val verticalOffset =
+                    offset(
+                        availableSize = containerSize.height,
+                        spacingPerDot = spacing,
+                        dotCount = rowCount,
+                        isCentered = centerDotsVertically,
+                    )
+                offset = Offset(horizontalOffset, verticalOffset)
+                scale = (colCount * spacing) / containerSize.width
 
-            if (isAnimationEnabled) {
-                // Draw lines between dots.
-                selectedDots.forEachIndexed { index, dot ->
-                    if (index > 0) {
-                        val previousDot = selectedDots[index - 1]
-                        val lineFadeOutAnimationProgress =
-                            lineFadeOutAnimatables[previousDot]!!.value
-                        val startLerp = 1 - lineFadeOutAnimationProgress
-                        val from =
-                            pixelOffset(previousDot, spacing, horizontalOffset, verticalOffset)
-                        val to = pixelOffset(dot, spacing, horizontalOffset, verticalOffset)
-                        val lerpedFrom =
-                            Offset(
-                                x = from.x + (to.x - from.x) * startLerp,
-                                y = from.y + (to.y - from.y) * startLerp,
+                if (isAnimationEnabled) {
+                    // Draw lines between dots.
+                    selectedDots.forEachIndexed { index, dot ->
+                        if (index > 0) {
+                            val previousDot = selectedDots[index - 1]
+                            val lineFadeOutAnimationProgress =
+                                lineFadeOutAnimatables[previousDot]!!.value
+                            val startLerp = 1 - lineFadeOutAnimationProgress
+                            val from =
+                                pixelOffset(previousDot, spacing, horizontalOffset, verticalOffset)
+                            val to = pixelOffset(dot, spacing, horizontalOffset, verticalOffset)
+                            val lerpedFrom =
+                                Offset(
+                                    x = from.x + (to.x - from.x) * startLerp,
+                                    y = from.y + (to.y - from.y) * startLerp,
+                                )
+                            drawLine(
+                                start = lerpedFrom,
+                                end = to,
+                                cap = StrokeCap.Round,
+                                alpha = lineFadeOutAnimationProgress * lineAlpha(spacing),
+                                color = lineColor,
+                                strokeWidth = lineStrokeWidth,
                             )
-                        drawLine(
-                            start = lerpedFrom,
-                            end = to,
-                            cap = StrokeCap.Round,
-                            alpha = lineFadeOutAnimationProgress * lineAlpha(spacing),
-                            color = lineColor,
-                            strokeWidth = lineStrokeWidth,
-                        )
+                        }
+                    }
+
+                    // Draw the line between the most recently-selected dot and the input pointer
+                    // position.
+                    inputPosition?.let { lineEnd ->
+                        currentDot?.let { dot ->
+                            val from = pixelOffset(dot, spacing, horizontalOffset, verticalOffset)
+                            val lineLength =
+                                sqrt((from.y - lineEnd.y).pow(2) + (from.x - lineEnd.x).pow(2))
+                            drawLine(
+                                start = from,
+                                end = lineEnd,
+                                cap = StrokeCap.Round,
+                                alpha = lineAlpha(spacing, lineLength),
+                                color = lineColor,
+                                strokeWidth = lineStrokeWidth,
+                            )
+                        }
                     }
                 }
 
-                // Draw the line between the most recently-selected dot and the input pointer
-                // position.
-                inputPosition?.let { lineEnd ->
-                    currentDot?.let { dot ->
-                        val from = pixelOffset(dot, spacing, horizontalOffset, verticalOffset)
-                        val lineLength =
-                            sqrt((from.y - lineEnd.y).pow(2) + (from.x - lineEnd.x).pow(2))
-                        drawLine(
-                            start = from,
-                            end = lineEnd,
-                            cap = StrokeCap.Round,
-                            alpha = lineAlpha(spacing, lineLength),
-                            color = lineColor,
-                            strokeWidth = lineStrokeWidth,
-                        )
-                    }
+                // Draw each dot on the grid.
+                dots.forEach { dot ->
+                    val initialOffset = checkNotNull(dotAppearMaxOffsetPixels[dot])
+                    val appearOffset =
+                        (1 - checkNotNull(dotAppearMoveUpAnimatables[dot]).value) * initialOffset
+                    drawCircle(
+                        center =
+                            pixelOffset(
+                                dot,
+                                spacing,
+                                horizontalOffset,
+                                verticalOffset + appearOffset,
+                            ),
+                        color =
+                            dotColor.copy(
+                                alpha = checkNotNull(dotAppearFadeInAnimatables[dot]).value
+                            ),
+                        radius = dotRadius * checkNotNull(dotScalingAnimatables[dot]).value,
+                    )
                 }
             }
-
-            // Draw each dot on the grid.
-            dots.forEach { dot ->
-                val initialOffset = checkNotNull(dotAppearMaxOffsetPixels[dot])
-                val appearOffset =
-                    (1 - checkNotNull(dotAppearMoveUpAnimatables[dot]).value) * initialOffset
-                drawCircle(
-                    center =
-                        pixelOffset(dot, spacing, horizontalOffset, verticalOffset + appearOffset),
-                    color =
-                        dotColor.copy(alpha = checkNotNull(dotAppearFadeInAnimatables[dot]).value),
-                    radius = dotRadius * checkNotNull(dotScalingAnimatables[dot]).value,
-                )
-            }
         }
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalTouchableSurface.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalTouchableSurface.kt
index f2edec6..3ae5036 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalTouchableSurface.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalTouchableSurface.kt
@@ -27,9 +27,14 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.input.key.onPreviewKeyEvent
 import androidx.compose.ui.input.pointer.motionEventSpy
-import androidx.compose.ui.semantics.hideFromAccessibility
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.semantics.CustomAccessibilityAction
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.customActions
 import androidx.compose.ui.semantics.semantics
+import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.res.R
 
 @OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
 @Composable
@@ -38,15 +43,38 @@
     modifier: Modifier = Modifier,
     content: @Composable BoxScope.() -> Unit,
 ) {
-
+    val context = LocalContext.current
     val interactionSource = remember { MutableInteractionSource() }
 
     Box(
         modifier =
             modifier
-                // The touchable surface is hidden for accessibility because these actions are
-                // already provided through custom accessibility actions.
-                .semantics { hideFromAccessibility() }
+                .semantics {
+                    contentDescription =
+                        context.getString(
+                            R.string.accessibility_content_description_for_communal_hub
+                        )
+                    customActions =
+                        listOf(
+                            CustomAccessibilityAction(
+                                context.getString(
+                                    R.string.accessibility_action_label_close_communal_hub
+                                )
+                            ) {
+                                viewModel.changeScene(
+                                    CommunalScenes.Blank,
+                                    "closed by accessibility",
+                                )
+                                true
+                            },
+                            CustomAccessibilityAction(
+                                context.getString(R.string.accessibility_action_label_edit_widgets)
+                            ) {
+                                viewModel.onOpenWidgetEditor()
+                                true
+                            },
+                        )
+                }
                 .combinedClickable(
                     onLongClick = viewModel::onLongClick,
                     onClick = viewModel::onClick,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
index f7ce215..7f7273d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
@@ -75,6 +75,7 @@
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.modifiers.fadingBackground
 import com.android.compose.theme.colorAttr
+import com.android.systemui.Flags.notificationShadeBlur
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.ui.compose.Icon
@@ -163,14 +164,16 @@
         }
     }
 
-    val backgroundColor = colorAttr(R.attr.underSurface)
+    val backgroundColor =
+        if (!notificationShadeBlur()) colorAttr(R.attr.underSurface) else Color.Transparent
+    val backgroundAlphaValue = if (!notificationShadeBlur()) backgroundAlpha::value else ({ 0f })
     val contentColor = MaterialTheme.colorScheme.onSurface
     val backgroundTopRadius = dimensionResource(R.dimen.qs_corner_radius)
     val backgroundModifier =
-        remember(backgroundColor, backgroundAlpha, backgroundTopRadius) {
+        remember(backgroundColor, backgroundAlphaValue, backgroundTopRadius) {
             Modifier.fadingBackground(
                 backgroundColor,
-                backgroundAlpha::value,
+                backgroundAlphaValue,
                 RoundedCornerShape(topStart = backgroundTopRadius, topEnd = backgroundTopRadius),
             )
         }
@@ -305,7 +308,8 @@
     ) {
         Box(Modifier.size(40.dp)) {
             Box(
-                Modifier.fillMaxSize()
+                Modifier
+                    .fillMaxSize()
                     .clip(CircleShape)
                     .indication(interactionSource, LocalIndication.current)
             ) {
@@ -333,7 +337,9 @@
     val contentDescription = stringResource(R.string.fgs_dot_content_description)
     val color = MaterialTheme.colorScheme.tertiary
 
-    Canvas(modifier.size(12.dp).semantics { this.contentDescription = contentDescription }) {
+    Canvas(modifier
+        .size(12.dp)
+        .semantics { this.contentDescription = contentDescription }) {
         drawCircle(color)
     }
 }
@@ -362,7 +368,9 @@
             Modifier.padding(horizontal = dimensionResource(R.dimen.qs_footer_padding)),
             verticalAlignment = Alignment.CenterVertically,
         ) {
-            Icon(icon, Modifier.padding(end = 12.dp).size(20.dp))
+            Icon(icon, Modifier
+                .padding(end = 12.dp)
+                .size(20.dp))
 
             Text(
                 text,
@@ -383,7 +391,9 @@
                 Icon(
                     painterResource(com.android.internal.R.drawable.ic_chevron_end),
                     contentDescription = null,
-                    Modifier.padding(start = 8.dp).size(20.dp),
+                    Modifier
+                        .padding(start = 8.dp)
+                        .size(20.dp),
                 )
             }
         }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
index f052e60..50bae8a0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
@@ -31,10 +31,14 @@
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.boundsInWindow
+import androidx.compose.ui.layout.onPlaced
+import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.scene.ContentScope
@@ -59,6 +63,8 @@
 import com.android.systemui.scene.ui.composable.Overlay
 import com.android.systemui.shade.ui.composable.CollapsedShadeHeader
 import com.android.systemui.shade.ui.composable.OverlayShade
+import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
+import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
 import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
 import com.android.systemui.statusbar.phone.ui.StatusBarIconController
@@ -96,13 +102,37 @@
     override fun ContentScope.Content(modifier: Modifier) {
         val viewModel =
             rememberViewModel("QuickSettingsShadeOverlay") { contentViewModelFactory.create() }
+        val panelCornerRadius =
+            with(LocalDensity.current) { OverlayShade.Dimensions.PanelCornerRadius.toPx().toInt() }
+
+        // set the bounds to null when the QuickSettings overlay disappears
+        DisposableEffect(Unit) { onDispose { viewModel.onPanelShapeChanged(null) } }
 
         OverlayShade(
             panelAlignment = Alignment.TopEnd,
             modifier = modifier,
             onScrimClicked = viewModel::onScrimClicked,
         ) {
-            Column {
+            Column(
+                modifier =
+                    Modifier.onPlaced { coordinates ->
+                        val boundsInWindow = coordinates.boundsInWindow()
+                        val shadeScrimBounds =
+                            ShadeScrimBounds(
+                                left = boundsInWindow.left,
+                                top = boundsInWindow.top,
+                                right = boundsInWindow.right,
+                                bottom = boundsInWindow.bottom,
+                            )
+                        val shape =
+                            ShadeScrimShape(
+                                bounds = shadeScrimBounds,
+                                topRadius = 0,
+                                bottomRadius = panelCornerRadius,
+                            )
+                        viewModel.onPanelShapeChanged(shape)
+                    }
+            ) {
                 if (viewModel.showHeader) {
                     CollapsedShadeHeader(
                         viewModelFactory = viewModel.shadeHeaderViewModelFactory,
@@ -112,7 +142,6 @@
                         statusBarIconController = statusBarIconController,
                     )
                 }
-
                 ShadeBody(viewModel = viewModel.quickSettingsContainerViewModel)
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorLocation.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/SensorLocation.kt
similarity index 64%
rename from packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorLocation.kt
rename to packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/SensorLocation.kt
index 2f2f3a3..5526792 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorLocation.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/SensorLocation.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.biometrics.shared.model
+package com.android.systemui.shared.customization.data
 
 /**
  * Provides current sensor location information in the current screen resolution [scale].
@@ -26,18 +26,40 @@
     private val naturalCenterX: Int,
     private val naturalCenterY: Int,
     private val naturalRadius: Int,
-    private val scale: Float = 1f
+    private val scale: Float = 1f,
 ) {
     val centerX: Float
         get() {
             return naturalCenterX * scale
         }
+
     val centerY: Float
         get() {
             return naturalCenterY * scale
         }
+
     val radius: Float
         get() {
             return naturalRadius * scale
         }
+
+    fun encode(): String {
+        return floatArrayOf(
+                naturalCenterX.toFloat(),
+                naturalCenterY.toFloat(),
+                naturalRadius.toFloat(),
+                scale,
+            )
+            .joinToString(DELIMITER)
+    }
+
+    companion object {
+
+        private const val DELIMITER: String = ","
+
+        fun decode(encoded: String): SensorLocation {
+            val array = encoded.split(DELIMITER).map { it.toFloat() }.toFloatArray()
+            return SensorLocation(array[0].toInt(), array[1].toInt(), array[2].toInt(), array[3])
+        }
+    }
 }
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt
index 48af2d9..caa6636 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt
@@ -80,13 +80,6 @@
     fun observeFlags(): Flow<List<Flag>>
 
     /**
-     * Returns [Flow] for observing the variables from the System UI.
-     *
-     * @see [queryRuntimeValues]
-     */
-    fun observeRuntimeValues(): Flow<Bundle>
-
-    /**
      * Returns all available affordances supported by the device, regardless of current slot
      * placement.
      */
@@ -291,6 +284,9 @@
                                     Contract.RuntimeValuesTable.KEY_IS_SHADE_LAYOUT_WIDE -> {
                                         putBoolean(name, cursor.getInt(valueColumnIndex) == 1)
                                     }
+                                    Contract.RuntimeValuesTable.KEY_UDFPS_LOCATION -> {
+                                        putString(name, cursor.getString(valueColumnIndex))
+                                    }
                                 }
                             }
                         }
@@ -307,10 +303,6 @@
         return observeUri(Contract.FlagsTable.URI).map { queryFlags() }
     }
 
-    override fun observeRuntimeValues(): Flow<Bundle> {
-        return observeUri(Contract.RuntimeValuesTable.URI).map { queryRuntimeValues() }
-    }
-
     override suspend fun queryAffordances(): List<CustomizationProviderClient.Affordance> {
         return withContext(backgroundDispatcher) {
             context.contentResolver
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
index cb167ed..2934f07 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
@@ -19,6 +19,7 @@
 
 import android.content.ContentResolver
 import android.net.Uri
+import com.android.systemui.shared.customization.data.SensorLocation
 
 /** Contract definitions for querying content about keyguard quick affordances. */
 object CustomizationProviderContract {
@@ -213,6 +214,11 @@
          * be as wide as the entire screen.
          */
         const val KEY_IS_SHADE_LAYOUT_WIDE = "is_shade_layout_wide"
+        /**
+         * This key corresponds to a String value, representing the string form of [SensorLocation],
+         * which contains the information of the UDFPS location.
+         */
+        const val KEY_UDFPS_LOCATION = "udfps_location"
 
         object Columns {
             /** String. Unique ID for the value. */
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/FakeCustomizationProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/FakeCustomizationProviderClient.kt
index 47c5bda..70d1782 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/FakeCustomizationProviderClient.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/FakeCustomizationProviderClient.kt
@@ -108,10 +108,6 @@
         return flags.asStateFlow()
     }
 
-    override fun observeRuntimeValues(): Flow<Bundle> {
-        return runtimeValues.asStateFlow()
-    }
-
     override suspend fun queryAffordances(): List<CustomizationProviderClient.Affordance> {
         return affordances.value
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
index 4d1660e..e26e19d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -79,8 +79,6 @@
     @Mock
     private LatencyTracker mLatencyTracker;
     @Mock
-    private LiftToActivateListener mLiftToactivateListener;
-    @Mock
     private EmergencyButtonController mEmergencyButtonController;
     private FalsingCollector mFalsingCollector = new FalsingCollectorFake();
     @Mock
@@ -122,7 +120,7 @@
         mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_REVAMPED_BOUNCER_MESSAGES);
         mKeyguardPinViewController = new KeyguardPinBasedInputViewController(mPinBasedInputView,
                 mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
-                mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener,
+                mKeyguardMessageAreaControllerFactory, mLatencyTracker,
                 mEmergencyButtonController, mFalsingCollector, featureFlags,
                 mSelectedUserInteractor, keyguardKeyboardInteractor, mBouncerHapticPlayer,
                 mUserActivityNotifier) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index 4d2a6d9..142a286 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -90,8 +90,6 @@
 
     @Mock private lateinit var mLatencyTracker: LatencyTracker
 
-    @Mock private lateinit var liftToActivateListener: LiftToActivateListener
-
     @Mock private val mEmergencyButtonController: EmergencyButtonController? = null
     private val falsingCollector: FalsingCollector = FalsingCollectorFake()
     private val keyguardKeyboardInteractor = KeyguardKeyboardInteractor(FakeKeyboardRepository())
@@ -147,7 +145,6 @@
             mKeyguardSecurityCallback,
             keyguardMessageAreaControllerFactory,
             mLatencyTracker,
-            liftToActivateListener,
             mEmergencyButtonController,
             falsingCollector,
             postureController,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
index 9cd5215..c751a7d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
@@ -63,7 +63,6 @@
     @Mock private lateinit var keyguardSecurityCallback: KeyguardSecurityCallback
     @Mock private lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory
     @Mock private lateinit var latencyTracker: LatencyTracker
-    @Mock private lateinit var liftToActivateListener: LiftToActivateListener
     @Mock private lateinit var telephonyManager: TelephonyManager
     @Mock private lateinit var falsingCollector: FalsingCollector
     @Mock private lateinit var emergencyButtonController: EmergencyButtonController
@@ -100,7 +99,6 @@
                 keyguardSecurityCallback,
                 messageAreaControllerFactory,
                 latencyTracker,
-                liftToActivateListener,
                 telephonyManager,
                 falsingCollector,
                 emergencyButtonController,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
index 3c22997..c346825 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
@@ -57,7 +57,6 @@
     @Mock private lateinit var keyguardSecurityCallback: KeyguardSecurityCallback
     @Mock private lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory
     @Mock private lateinit var latencyTracker: LatencyTracker
-    @Mock private lateinit var liftToActivateListener: LiftToActivateListener
     @Mock private lateinit var telephonyManager: TelephonyManager
     @Mock private lateinit var falsingCollector: FalsingCollector
     @Mock private lateinit var emergencyButtonController: EmergencyButtonController
@@ -95,7 +94,6 @@
                 keyguardSecurityCallback,
                 messageAreaControllerFactory,
                 latencyTracker,
-                liftToActivateListener,
                 telephonyManager,
                 falsingCollector,
                 emergencyButtonController,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
index 56a97bb..fff6def 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
@@ -28,6 +28,7 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
+import android.graphics.PointF;
 import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.testing.TestableLooper;
@@ -210,7 +211,7 @@
         mTouchHandler.onInterceptTouchEvent(mStubListView, stubMoveEvent);
         mTouchHandler.onInterceptTouchEvent(mStubListView, stubUpEvent);
 
-        verify(mMenuAnimationController).flingMenuThenSpringToEdge(anyFloat(), anyFloat(),
+        verify(mMenuAnimationController).flingMenuThenSpringToEdge(any(PointF.class), anyFloat(),
                 anyFloat());
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
index 5ff7bd0..737170f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
@@ -18,17 +18,25 @@
 
 import static android.app.UiModeManager.MODE_NIGHT_YES;
 
+import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.app.UiModeManager;
+import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.graphics.drawable.GradientDrawable;
+import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.testing.TestableLooper;
 import android.view.WindowManager;
@@ -37,6 +45,8 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.accessibility.common.ShortcutConstants;
+import com.android.internal.accessibility.dialog.AccessibilityTarget;
 import com.android.settingslib.bluetooth.HearingAidDeviceManager;
 import com.android.systemui.Flags;
 import com.android.systemui.Prefs;
@@ -54,6 +64,9 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /** Tests for {@link MenuView}. */
 @RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -63,17 +76,24 @@
     private int mNightMode;
     private UiModeManager mUiModeManager;
     private MenuView mMenuView;
+    private MenuView mMenuViewSpy;
     private String mLastPosition;
     private MenuViewAppearance mStubMenuViewAppearance;
+    private MenuViewModel mMenuViewModel;
+    private final List<String> mShortcutTargets = new ArrayList<>();
 
     @Rule
     public MockitoRule mockito = MockitoJUnit.rule();
 
     @Mock
     private AccessibilityManager mAccessibilityManager;
+
     @Mock
     private HearingAidDeviceManager mHearingAidDeviceManager;
 
+    @Mock
+    private MenuView.OnTargetFeaturesChangeListener mOnTargetFeaturesChangeListener;
+
     private SysuiTestableContext mSpyContext;
 
     @Before
@@ -91,22 +111,38 @@
         mSpyContext = spy(mContext);
         doNothing().when(mSpyContext).startActivity(any());
 
+        mContext.addMockSystemService(Context.ACCESSIBILITY_SERVICE, mAccessibilityManager);
+        mShortcutTargets.add(MAGNIFICATION_CONTROLLER_NAME);
+        doReturn(mShortcutTargets)
+                .when(mAccessibilityManager)
+                .getAccessibilityShortcutTargets(anyInt());
+
         final SecureSettings secureSettings = TestUtils.mockSecureSettings(mContext);
-        final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
-                secureSettings, mHearingAidDeviceManager);
+        mMenuViewModel =
+                new MenuViewModel(
+                    mContext, mAccessibilityManager, secureSettings, mHearingAidDeviceManager);
         final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
         mStubMenuViewAppearance = new MenuViewAppearance(mSpyContext, stubWindowManager);
-        mMenuView = spy(new MenuView(mSpyContext, stubMenuViewModel, mStubMenuViewAppearance,
-                secureSettings));
+        mMenuView =
+                new MenuView(mSpyContext, mMenuViewModel, mStubMenuViewAppearance, secureSettings);
+        mMenuView.setOnTargetFeaturesChangeListener(mOnTargetFeaturesChangeListener);
         mLastPosition = Prefs.getString(mSpyContext,
                 Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION, /* defaultValue= */ null);
+
+        mMenuViewSpy =
+                spy(
+                        new MenuView(
+                                mSpyContext,
+                                mMenuViewModel,
+                                mStubMenuViewAppearance,
+                                secureSettings));
     }
 
     @Test
     public void onConfigurationChanged_updateViewModel() {
-        mMenuView.onConfigurationChanged(/* newConfig= */ null);
+        mMenuViewSpy.onConfigurationChanged(/* newConfig= */ null);
 
-        verify(mMenuView).loadLayoutResources();
+        verify(mMenuViewSpy).loadLayoutResources();
     }
 
     @Test
@@ -179,6 +215,75 @@
         assertThat(radiiAnimator.isStarted()).isTrue();
     }
 
+    @Test
+    @DisableFlags(Flags.FLAG_FLOATING_MENU_NOTIFY_TARGETS_CHANGED_ON_STRICT_DIFF)
+    public void onTargetFeaturesChanged_listenerCalled_flagDisabled() {
+        // Call show() to start observing the target features change listener.
+        mMenuView.show();
+
+        // The target features change listener should be called when the observer is added.
+        verify(mOnTargetFeaturesChangeListener, times(1)).onChange(any());
+
+        // When the target features list changes, the listener should be called.
+        mMenuViewModel.onTargetFeaturesChanged(
+                List.of(
+                        new TestAccessibilityTarget(mContext, 123),
+                        new TestAccessibilityTarget(mContext, 456)));
+        verify(mOnTargetFeaturesChangeListener, times(2)).onChange(any());
+
+        // Double check that when the target features list changes, the listener should be called.
+        List<AccessibilityTarget> newFeaturesList =
+                List.of(
+                        new TestAccessibilityTarget(mContext, 123),
+                        new TestAccessibilityTarget(mContext, 789),
+                        new TestAccessibilityTarget(mContext, 456));
+        mMenuViewModel.onTargetFeaturesChanged(newFeaturesList);
+        verify(mOnTargetFeaturesChangeListener, times(3)).onChange(any());
+
+        // When the target features list doesn't change, the listener will still be called.
+        mMenuViewModel.onTargetFeaturesChanged(newFeaturesList);
+        verify(mOnTargetFeaturesChangeListener, times(4)).onChange(any());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_FLOATING_MENU_NOTIFY_TARGETS_CHANGED_ON_STRICT_DIFF)
+    public void onTargetFeaturesChanged_listenerCalled_flagEnabled() {
+        // Call show() to start observing the target features change listener.
+        mMenuView.show();
+
+        // The target features change listener should be called when the observer is added.
+        verify(mOnTargetFeaturesChangeListener, times(1)).onChange(any());
+
+        // When the target features list changes, the listener should be called.
+        mMenuViewModel.onTargetFeaturesChanged(
+                List.of(
+                        new TestAccessibilityTarget(mContext, 123),
+                        new TestAccessibilityTarget(mContext, 456)));
+        verify(mOnTargetFeaturesChangeListener, times(2)).onChange(any());
+
+        // Double check that when the target features list changes, the listener should be called.
+        List<AccessibilityTarget> newFeaturesList =
+                List.of(
+                        new TestAccessibilityTarget(mContext, 123),
+                        new TestAccessibilityTarget(mContext, 789),
+                        new TestAccessibilityTarget(mContext, 456));
+        mMenuViewModel.onTargetFeaturesChanged(newFeaturesList);
+        verify(mOnTargetFeaturesChangeListener, times(3)).onChange(any());
+
+        // When the target features list doesn't change, the listener should not be called again.
+        mMenuViewModel.onTargetFeaturesChanged(newFeaturesList);
+        verify(mOnTargetFeaturesChangeListener, times(3)).onChange(any());
+
+        // When the target features list changes order (but the UIDs of the targets don't change),
+        // the listener should be called.
+        mMenuViewModel.onTargetFeaturesChanged(
+                List.of(
+                        new TestAccessibilityTarget(mContext, 789),
+                        new TestAccessibilityTarget(mContext, 123),
+                        new TestAccessibilityTarget(mContext, 456)));
+        verify(mOnTargetFeaturesChangeListener, times(4)).onChange(any());
+    }
+
     private InstantInsetLayerDrawable getMenuViewInsetLayer() {
         return (InstantInsetLayerDrawable) mMenuView.getBackground();
     }
@@ -196,6 +301,23 @@
         return radiiAnimator;
     }
 
+    /** Simplified AccessibilityTarget for testing MenuView. */
+    private static class TestAccessibilityTarget extends AccessibilityTarget {
+        TestAccessibilityTarget(Context context, int uid) {
+            // Set fields unused by tests to defaults that allow test compilation.
+            super(
+                    context,
+                    ShortcutConstants.UserShortcutType.SOFTWARE,
+                    0,
+                    false,
+                    MAGNIFICATION_COMPONENT_NAME.flattenToString(),
+                    uid,
+                    null,
+                    null,
+                    null);
+        }
+    }
+
     @After
     public void tearDown() throws Exception {
         mUiModeManager.setNightMode(mNightMode);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
index 43d0d69c..e4539b7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
@@ -66,7 +66,6 @@
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
 import com.android.settingslib.bluetooth.VolumeControlProfile;
-import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.DialogTransitionAnimator;
 import com.android.systemui.bluetooth.qsdialog.DeviceItem;
@@ -227,7 +226,6 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_HEARING_DEVICES_DIALOG_RELATED_TOOLS)
     public void showDialog_noLiveCaption_noRelatedToolsInConfig_relatedToolLayoutGone() {
         mContext.getOrCreateTestableResources().addOverride(
                 R.array.config_quickSettingsHearingDevicesRelatedToolName, new String[]{});
@@ -239,7 +237,6 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_HEARING_DEVICES_DIALOG_RELATED_TOOLS)
     public void showDialog_hasLiveCaption_noRelatedToolsInConfig_showOneRelatedTool() {
         when(mPackageManager.queryIntentActivities(
                 eq(LIVE_CAPTION_INTENT), anyInt())).thenReturn(
@@ -254,7 +251,6 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_HEARING_DEVICES_DIALOG_RELATED_TOOLS)
     public void showDialog_hasLiveCaption_oneRelatedToolInConfig_showTwoRelatedTools()
             throws PackageManager.NameNotFoundException {
         when(mPackageManager.queryIntentActivities(eq(LIVE_CAPTION_INTENT), anyInt()))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt
new file mode 100644
index 0000000..fbb0fee
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt
@@ -0,0 +1,94 @@
+/*
+ * 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.bouncer.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.time.SystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardBouncerRepositoryTest : SysuiTestCase() {
+
+    @Mock private lateinit var systemClock: SystemClock
+    @Mock private lateinit var bouncerLogger: TableLogBuffer
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    lateinit var underTest: KeyguardBouncerRepository
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        underTest =
+            object :
+                KeyguardBouncerRepositoryImpl(
+                    systemClock,
+                    testScope.backgroundScope,
+                    bouncerLogger,
+                ) {
+                override fun isDebuggable(): Boolean = true
+            }
+    }
+
+    @Test
+    fun changingFlowValueTriggersLogging() =
+        testScope.runTest {
+            underTest.setPrimaryShow(true)
+            runCurrent()
+            Mockito.verify(bouncerLogger)
+                .logChange(eq(""), eq("PrimaryBouncerShow"), value = eq(false), any())
+        }
+
+    @Test
+    fun primaryStartDisappearAnimation() =
+        testScope.runTest {
+            assertThat(underTest.isPrimaryBouncerStartingDisappearAnimation()).isFalse()
+
+            underTest.setPrimaryStartDisappearAnimation(Runnable {})
+            assertThat(underTest.isPrimaryBouncerStartingDisappearAnimation()).isTrue()
+
+            underTest.setPrimaryStartDisappearAnimation(null)
+            assertThat(underTest.isPrimaryBouncerStartingDisappearAnimation()).isFalse()
+
+            val disappearFlow by collectValues(underTest.primaryBouncerStartingDisappearAnimation)
+            underTest.setPrimaryStartDisappearAnimation(null)
+            assertThat(disappearFlow[0]).isNull()
+
+            // Now issue two in a row to make sure one is not dropped
+            underTest.setPrimaryStartDisappearAnimation(Runnable {})
+            underTest.setPrimaryStartDisappearAnimation(null)
+            assertThat(disappearFlow[1]).isNotNull()
+            assertThat(disappearFlow[2]).isNull()
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
index d5e1fae..c1feca2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -105,7 +105,7 @@
                 mSelectedUserInteractor,
                 faceAuthInteractor,
             )
-        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+        whenever(repository.isPrimaryBouncerStartingDisappearAnimation()).thenReturn(false)
         whenever(repository.primaryBouncerShow.value).thenReturn(false)
         whenever(bouncerView.delegate).thenReturn(bouncerViewDelegate)
         resources = context.orCreateTestableResources
@@ -199,7 +199,6 @@
     @Test
     fun testExpansion_fullyShown() {
         whenever(repository.panelExpansionAmount.value).thenReturn(0.5f)
-        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
         underTest.setPanelExpansion(EXPANSION_VISIBLE)
         verify(falsingCollector).onBouncerShown()
         verify(mPrimaryBouncerCallbackInteractor).dispatchFullyShown()
@@ -208,7 +207,6 @@
     @Test
     fun testExpansion_fullyHidden() {
         whenever(repository.panelExpansionAmount.value).thenReturn(0.5f)
-        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
         underTest.setPanelExpansion(EXPANSION_HIDDEN)
         verify(repository).setPrimaryShow(false)
         verify(falsingCollector).onBouncerHidden()
@@ -307,7 +305,6 @@
     fun testIsFullShowing() {
         whenever(repository.primaryBouncerShow.value).thenReturn(true)
         whenever(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
-        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
         assertThat(underTest.isFullyShowing()).isTrue()
         whenever(repository.primaryBouncerShow.value).thenReturn(false)
         assertThat(underTest.isFullyShowing()).isFalse()
@@ -333,9 +330,9 @@
 
     @Test
     fun testIsAnimatingAway() {
-        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(Runnable {})
+        whenever(repository.isPrimaryBouncerStartingDisappearAnimation()).thenReturn(true)
         assertThat(underTest.isAnimatingAway()).isTrue()
-        whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+        whenever(repository.isPrimaryBouncerStartingDisappearAnimation()).thenReturn(false)
         assertThat(underTest.isAnimatingAway()).isFalse()
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ClipboardImageLoaderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ClipboardImageLoaderTest.kt
index 791f1f2..6fdeb2b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ClipboardImageLoaderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/clipboardoverlay/ClipboardImageLoaderTest.kt
@@ -17,14 +17,17 @@
 
 import android.content.ContentResolver
 import android.content.Context
+import android.content.pm.UserInfo
 import android.net.Uri
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_CLIPBOARD_OVERLAY_MULTIUSER
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.util.mockito.whenever
+import com.android.systemui.settings.FakeUserTracker
 import java.io.IOException
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.runTest
 import org.junit.Assert.assertNull
@@ -36,44 +39,90 @@
 import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.whenever
 
 @SmallTest
-@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 class ClipboardImageLoaderTest : SysuiTestCase() {
     @Mock private lateinit var mockContext: Context
 
     @Mock private lateinit var mockContentResolver: ContentResolver
+    @Mock private lateinit var mockSecondaryContentResolver: ContentResolver
 
     private lateinit var clipboardImageLoader: ClipboardImageLoader
+    private var fakeUserTracker: FakeUserTracker =
+        FakeUserTracker(userContentResolverProvider = { mockContentResolver })
+
+    private val userInfos = listOf(UserInfo(0, "system", 0), UserInfo(50, "secondary", 0))
 
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
+
+        fakeUserTracker.set(userInfos, 0)
     }
 
     @Test
     @Throws(IOException::class)
-    fun test_imageLoadSuccess() = runTest {
+    @DisableFlags(FLAG_CLIPBOARD_OVERLAY_MULTIUSER)
+    fun test_imageLoadSuccess_legacy() = runTest {
         val testDispatcher = StandardTestDispatcher(this.testScheduler)
+        fakeUserTracker =
+            FakeUserTracker(userContentResolverProvider = { mockSecondaryContentResolver })
+        fakeUserTracker.set(userInfos, 1)
+
         clipboardImageLoader =
-            ClipboardImageLoader(mockContext, testDispatcher, CoroutineScope(testDispatcher))
+            ClipboardImageLoader(
+                mockContext,
+                fakeUserTracker,
+                testDispatcher,
+                CoroutineScope(testDispatcher),
+            )
         val testUri = Uri.parse("testUri")
-        whenever(mockContext.contentResolver).thenReturn(mockContentResolver)
+        whenever<ContentResolver?>(mockContext.contentResolver)
+            .thenReturn(mockSecondaryContentResolver)
         whenever(mockContext.resources).thenReturn(context.resources)
 
         clipboardImageLoader.load(testUri)
 
-        verify(mockContentResolver).loadThumbnail(eq(testUri), any(), any())
+        verify(mockSecondaryContentResolver).loadThumbnail(eq(testUri), any(), any())
     }
 
-    @OptIn(ExperimentalCoroutinesApi::class)
+    @Test
+    @Throws(IOException::class)
+    @EnableFlags(FLAG_CLIPBOARD_OVERLAY_MULTIUSER)
+    fun test_imageLoadSuccess() = runTest {
+        val testDispatcher = StandardTestDispatcher(this.testScheduler)
+        fakeUserTracker =
+            FakeUserTracker(userContentResolverProvider = { mockSecondaryContentResolver })
+        fakeUserTracker.set(userInfos, 1)
+
+        clipboardImageLoader =
+            ClipboardImageLoader(
+                mockContext,
+                fakeUserTracker,
+                testDispatcher,
+                CoroutineScope(testDispatcher),
+            )
+        val testUri = Uri.parse("testUri")
+        whenever(mockContext.resources).thenReturn(context.resources)
+
+        clipboardImageLoader.load(testUri)
+
+        verify(mockSecondaryContentResolver).loadThumbnail(eq(testUri), any(), any())
+    }
+
     @Test
     @Throws(IOException::class)
     fun test_imageLoadFailure() = runTest {
         val testDispatcher = StandardTestDispatcher(this.testScheduler)
         clipboardImageLoader =
-            ClipboardImageLoader(mockContext, testDispatcher, CoroutineScope(testDispatcher))
+            ClipboardImageLoader(
+                mockContext,
+                fakeUserTracker,
+                testDispatcher,
+                CoroutineScope(testDispatcher),
+            )
         val testUri = Uri.parse("testUri")
         whenever(mockContext.contentResolver).thenReturn(mockContentResolver)
         whenever(mockContext.resources).thenReturn(context.resources)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt
index ca7e203..26859b6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt
@@ -190,11 +190,8 @@
             lockDevice()
         }
 
-        if (shadeMode == ShadeMode.Dual) {
-            assertThat(DualShade.isEnabled).isTrue()
-        } else {
-            assertThat(DualShade.isEnabled).isFalse()
-            kosmos.fakeShadeRepository.setShadeLayoutWide(shadeMode == ShadeMode.Split)
+        if (shadeMode !is ShadeMode.Dual) {
+            kosmos.fakeShadeRepository.setShadeLayoutWide(shadeMode is ShadeMode.Split)
         }
         runCurrent()
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt
index d6daa79..e19ea36 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt
@@ -316,11 +316,8 @@
             lockDevice()
         }
 
-        if (shadeMode == ShadeMode.Dual) {
-            assertThat(DualShade.isEnabled).isTrue()
-        } else {
-            assertThat(DualShade.isEnabled).isFalse()
-            kosmos.fakeShadeRepository.setShadeLayoutWide(shadeMode == ShadeMode.Split)
+        if (shadeMode !is ShadeMode.Dual) {
+            kosmos.fakeShadeRepository.setShadeLayoutWide(shadeMode is ShadeMode.Split)
         }
         runCurrent()
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableListTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableListTest.kt
index 16f30fe..9c630eb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableListTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableListTest.kt
@@ -17,9 +17,6 @@
 package com.android.systemui.qs.pipeline.domain.autoaddable
 
 import android.content.ComponentName
-import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
-import android.view.accessibility.Flags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.accessibility.AccessibilityShortcutController
@@ -43,44 +40,35 @@
         object : A11yShortcutAutoAddable.Factory {
             override fun create(
                 spec: TileSpec,
-                componentName: ComponentName
+                componentName: ComponentName,
             ): A11yShortcutAutoAddable {
                 return A11yShortcutAutoAddable(mock(), mock(), spec, componentName)
             }
         }
 
     @Test
-    @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
-    fun getA11yShortcutAutoAddables_withA11yQsShortcutFlagOff_emptyResult() {
-        val autoAddables = A11yShortcutAutoAddableList.getA11yShortcutAutoAddables(factory)
-
-        assertThat(autoAddables).isEmpty()
-    }
-
-    @Test
-    @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
-    fun getA11yShortcutAutoAddables_withA11yQsShortcutFlagOn_correctAutoAddables() {
+    fun getA11yShortcutAutoAddables_correctAutoAddables() {
         val expected =
             setOf(
                 factory.create(
                     TileSpec.create(ColorCorrectionTile.TILE_SPEC),
-                    AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME
+                    AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME,
                 ),
                 factory.create(
                     TileSpec.create(ColorInversionTile.TILE_SPEC),
-                    AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME
+                    AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME,
                 ),
                 factory.create(
                     TileSpec.create(OneHandedModeTile.TILE_SPEC),
-                    AccessibilityShortcutController.ONE_HANDED_COMPONENT_NAME
+                    AccessibilityShortcutController.ONE_HANDED_COMPONENT_NAME,
                 ),
                 factory.create(
                     TileSpec.create(ReduceBrightColorsTile.TILE_SPEC),
-                    AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME
+                    AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME,
                 ),
                 factory.create(
                     TileSpec.create(HearingDevicesTile.TILE_SPEC),
-                    AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME
+                    AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME,
                 ),
             )
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt
deleted file mode 100644
index d0699aa..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs.pipeline.domain.autoaddable
-
-import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
-import android.view.accessibility.Flags
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.qs.ReduceBrightColorsController
-import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
-import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
-import com.android.systemui.qs.pipeline.shared.TileSpec
-import com.android.systemui.qs.tiles.ReduceBrightColorsTile
-import com.android.systemui.util.mockito.capture
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestResult
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.Captor
-import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class ReduceBrightColorsAutoAddableTest : SysuiTestCase() {
-
-    @Mock private lateinit var reduceBrightColorsController: ReduceBrightColorsController
-    @Captor
-    private lateinit var reduceBrightColorsListenerCaptor:
-        ArgumentCaptor<ReduceBrightColorsController.Listener>
-
-    private lateinit var underTest: ReduceBrightColorsAutoAddable
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-    }
-
-    @Test
-    fun notAvailable_strategyDisabled() =
-        testWithFeatureAvailability(available = false) {
-            assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.Disabled)
-        }
-
-    @Test
-    @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
-    fun available_strategyIfNotAdded() =
-        testWithFeatureAvailability(available = true) {
-            assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.IfNotAdded(SPEC))
-        }
-
-    @Test
-    @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
-    fun activated_addSignal() = testWithFeatureAvailability {
-        val signal by collectLastValue(underTest.autoAddSignal(0))
-        runCurrent()
-
-        verify(reduceBrightColorsController).addCallback(capture(reduceBrightColorsListenerCaptor))
-
-        reduceBrightColorsListenerCaptor.value.onActivated(true)
-
-        assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
-    }
-
-    @Test
-    @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
-    fun notActivated_noSignal() = testWithFeatureAvailability {
-        val signal by collectLastValue(underTest.autoAddSignal(0))
-        runCurrent()
-
-        verify(reduceBrightColorsController).addCallback(capture(reduceBrightColorsListenerCaptor))
-
-        reduceBrightColorsListenerCaptor.value.onActivated(false)
-
-        assertThat(signal).isNull()
-    }
-
-    @Test
-    @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
-    fun available_a11yQsShortcutFlagEnabled_strategyDisabled() =
-        testWithFeatureAvailability(available = true) {
-            assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.Disabled)
-        }
-
-    private fun testWithFeatureAvailability(
-        available: Boolean = true,
-        body: suspend TestScope.() -> TestResult
-    ) = runTest {
-        underTest = ReduceBrightColorsAutoAddable(reduceBrightColorsController, available)
-        body()
-    }
-
-    companion object {
-        private val SPEC by lazy { TileSpec.create(ReduceBrightColorsTile.TILE_SPEC) }
-    }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AccessibilityTilesInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AccessibilityTilesInteractorTest.kt
index 3faab50..6cd627c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AccessibilityTilesInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AccessibilityTilesInteractorTest.kt
@@ -19,9 +19,6 @@
 import android.content.Context
 import android.content.pm.UserInfo
 import android.os.UserHandle
-import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
-import android.view.accessibility.Flags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -50,21 +47,9 @@
 @RunWith(AndroidJUnit4::class)
 @OptIn(ExperimentalCoroutinesApi::class)
 class AccessibilityTilesInteractorTest : SysuiTestCase() {
-    private val USER_0_INFO =
-        UserInfo(
-            0,
-            "zero",
-            "",
-            UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL,
-        )
+    private val USER_0_INFO = UserInfo(0, "zero", "", UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL)
 
-    private val USER_1_INFO =
-        UserInfo(
-            1,
-            "one",
-            "",
-            UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL,
-        )
+    private val USER_1_INFO = UserInfo(1, "one", "", UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL)
 
     private val USER_0_TILES = listOf(TileSpec.create(ColorInversionTile.TILE_SPEC))
     private val USER_1_TILES = listOf(TileSpec.create(ColorCorrectionTile.TILE_SPEC))
@@ -94,20 +79,7 @@
     }
 
     @Test
-    @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
-    fun currentTilesChanged_a11yQsShortcutFlagOff_nothingHappen() =
-        testScope.runTest {
-            underTest = createInteractor()
-
-            setTiles(USER_0_TILES)
-            runCurrent()
-
-            assertThat(a11yQsShortcutsRepository.notifyA11yManagerTilesChangedRequests).isEmpty()
-        }
-
-    @Test
-    @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
-    fun currentTilesChanged_a11yQsShortcutFlagOn_notifyAccessibilityRepository() =
+    fun currentTilesChanged_notifyAccessibilityRepository() =
         testScope.runTest {
             underTest = createInteractor()
 
@@ -123,8 +95,7 @@
         }
 
     @Test
-    @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
-    fun userChanged_a11yQsShortcutFlagOn_notifyAccessibilityRepositoryWithCorrectTilesAndUser() =
+    fun userChanged_notifyAccessibilityRepositoryWithCorrectTilesAndUser() =
         testScope.runTest {
             underTest = createInteractor()
             setTiles(USER_0_TILES)
@@ -163,7 +134,7 @@
         return AccessibilityTilesInteractor(
                 a11yQsShortcutsRepository,
                 testDispatcher,
-                testScope.backgroundScope
+                testScope.backgroundScope,
             )
             .apply { init(currentTilesInteractor) }
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/HearingDevicesTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/HearingDevicesTileTest.java
index 7d41a20..307b87a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/HearingDevicesTileTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/HearingDevicesTileTest.java
@@ -27,8 +27,6 @@
 
 import android.content.Intent;
 import android.os.Handler;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
 import android.provider.Settings;
 import android.service.quicksettings.Tile;
 import android.testing.TestableLooper;
@@ -38,7 +36,6 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.accessibility.hearingaid.HearingDevicesChecker;
 import com.android.systemui.accessibility.hearingaid.HearingDevicesDialogManager;
@@ -124,18 +121,6 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_HEARING_AIDS_QS_TILE_DIALOG)
-    public void isAvailable_flagEnabled_true() {
-        assertThat(mTile.isAvailable()).isTrue();
-    }
-
-    @Test
-    @DisableFlags(Flags.FLAG_HEARING_AIDS_QS_TILE_DIALOG)
-    public void isAvailable_flagDisabled_false() {
-        assertThat(mTile.isAvailable()).isFalse();
-    }
-
-    @Test
     public void longClick_expectedAction() {
         mTile.longClick(null);
         mTestableLooper.processAllMessages();
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt
index 79556ba..fecd8c3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt
@@ -26,11 +26,9 @@
 import android.testing.TestableLooper.RunWithLooper
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
-import com.android.systemui.Flags
 import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingManagerFake
-import com.android.systemui.flags.setFlagValue
 import com.android.systemui.keyguard.KeyguardWmStateRefactor
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -42,7 +40,6 @@
 import com.android.systemui.qs.tiles.dialog.InternetDialogManager
 import com.android.systemui.qs.tiles.dialog.WifiStateWorker
 import com.android.systemui.res.R
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.shade.shared.flag.DualShade
 import com.android.systemui.statusbar.connectivity.AccessPointController
 import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
@@ -65,6 +62,7 @@
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
+import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -150,11 +148,18 @@
             )
 
         underTest.initialize()
+
         underTest.setListening(Object(), true)
 
         looper.processAllMessages()
     }
 
+    @After
+    fun tearDown() {
+        underTest.destroy()
+        looper.processAllMessages()
+    }
+
     @Test
     fun noDefaultConnection_noNetworkAvailable() =
         testScope.runTest {
@@ -272,33 +277,37 @@
         underTest.click(null)
         looper.processAllMessages()
 
-        verify(dialogManager, times(1)).create(
-            aboveStatusBar = true,
-            accessPointController.canConfigMobileData(),
-            accessPointController.canConfigWifi(),
-            null,
-        )
+        verify(dialogManager, times(1))
+            .create(
+                aboveStatusBar = true,
+                accessPointController.canConfigMobileData(),
+                accessPointController.canConfigWifi(),
+                null,
+            )
     }
 
     @Test
     @EnableFlags(
-        value = [
-            QsDetailedView.FLAG_NAME,
-            FLAG_SCENE_CONTAINER,
-            KeyguardWmStateRefactor.FLAG_NAME,
-            NotificationThrottleHun.FLAG_NAME,
-            DualShade.FLAG_NAME]
+        value =
+            [
+                QsDetailedView.FLAG_NAME,
+                FLAG_SCENE_CONTAINER,
+                KeyguardWmStateRefactor.FLAG_NAME,
+                NotificationThrottleHun.FLAG_NAME,
+                DualShade.FLAG_NAME,
+            ]
     )
     fun click_withQsDetailedViewEnabled() {
         underTest.click(null)
         looper.processAllMessages()
 
-        verify(dialogManager, times(0)).create(
-            aboveStatusBar = true,
-            accessPointController.canConfigMobileData(),
-            accessPointController.canConfigWifi(),
-            null,
-        )
+        verify(dialogManager, times(0))
+            .create(
+                aboveStatusBar = true,
+                accessPointController.canConfigMobileData(),
+                accessPointController.canConfigWifi(),
+                null,
+            )
     }
 
     companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
index c7da03d..497e335 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
@@ -46,6 +46,7 @@
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.Executors
+import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -123,6 +124,12 @@
             )
     }
 
+    @After
+    fun teardown() {
+        tile.destroy()
+        testableLooper.processAllMessages()
+    }
+
     @Test
     fun qsTileUi_shouldLookCorrect_whenInactive() {
         whenever(issueRecordingState.isRecording).thenReturn(false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileDataInteractorTest.kt
index 1dfa2cd..9099d3d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileDataInteractorTest.kt
@@ -17,12 +17,9 @@
 package com.android.systemui.qs.tiles.impl.hearingdevices.domain.interactor
 
 import android.os.UserHandle
-import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
 import android.platform.test.annotations.EnabledOnRavenwood
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.accessibility.hearingaid.HearingDevicesChecker
 import com.android.systemui.coroutines.collectLastValue
@@ -66,24 +63,14 @@
         underTest = HearingDevicesTileDataInteractor(testScope.testScheduler, controller, checker)
     }
 
-    @EnableFlags(Flags.FLAG_HEARING_AIDS_QS_TILE_DIALOG)
     @Test
-    fun availability_flagEnabled_returnTrue() =
+    fun availability_returnTrue() =
         testScope.runTest {
             val availability by collectLastValue(underTest.availability(testUser))
 
             assertThat(availability).isTrue()
         }
 
-    @DisableFlags(Flags.FLAG_HEARING_AIDS_QS_TILE_DIALOG)
-    @Test
-    fun availability_flagDisabled_returnFalse() =
-        testScope.runTest {
-            val availability by collectLastValue(underTest.availability(testUser))
-
-            assertThat(availability).isFalse()
-        }
-
     @Test
     fun tileData_bluetoothStateChanged_dataMatchesChecker() =
         testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
index 44e6b4d..029a2f9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
@@ -58,7 +58,9 @@
     private val dispatcher = kosmos.testDispatcher
     private val zenModeRepository = kosmos.fakeZenModeRepository
 
-    private val underTest = ModesTileDataInteractor(context, kosmos.zenModeInteractor, dispatcher)
+    private val underTest by lazy {
+        ModesTileDataInteractor(context, kosmos.zenModeInteractor, dispatcher)
+    }
 
     @Before
     fun setUp() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt
index 7ab8ab9..dce1102 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt
@@ -39,6 +39,9 @@
 import com.android.systemui.shade.data.repository.shadeRepository
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.shade.shared.flag.DualShade
+import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
+import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
+import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -143,6 +146,23 @@
             assertThat(underTest.showHeader).isFalse()
         }
 
+    @Test
+    fun onPanelShapeChanged() =
+        testScope.runTest {
+            val actual by
+                collectLastValue(kosmos.notificationStackAppearanceInteractor.qsPanelShape)
+            val expected =
+                ShadeScrimShape(
+                    bounds = ShadeScrimBounds(left = 10f, top = 0f, right = 710f, bottom = 600f),
+                    topRadius = 0,
+                    bottomRadius = 100,
+                )
+
+            underTest.onPanelShapeChanged(expected)
+
+            assertThat(expected).isEqualTo(actual)
+        }
+
     private fun TestScope.lockDevice() {
         val currentScene by collectLastValue(sceneInteractor.currentScene)
         kosmos.powerInteractor.setAsleepForTest()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 51f056a..cb7267b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -1508,10 +1508,8 @@
         }
 
     @Test
-    fun collectFalsingSignals_screenOnAndOff_aodUnavailable() =
+    fun collectFalsingSignals_screenOnAndOff() =
         testScope.runTest {
-            kosmos.fakeKeyguardRepository.setAodAvailable(false)
-            runCurrent()
             prepareState(
                 initialSceneKey = Scenes.Lockscreen,
                 authenticationMethod = AuthenticationMethodModel.Pin,
@@ -1556,53 +1554,6 @@
         }
 
     @Test
-    fun collectFalsingSignals_screenOnAndOff_aodAvailable() =
-        testScope.runTest {
-            kosmos.fakeKeyguardRepository.setAodAvailable(true)
-            runCurrent()
-            prepareState(
-                initialSceneKey = Scenes.Lockscreen,
-                authenticationMethod = AuthenticationMethodModel.Pin,
-                isDeviceUnlocked = false,
-            )
-            underTest.start()
-            runCurrent()
-            verify(falsingCollector, never()).onScreenTurningOn()
-            verify(falsingCollector, never()).onScreenOnFromTouch()
-            verify(falsingCollector, never()).onScreenOff()
-
-            powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_POWER_BUTTON)
-            runCurrent()
-            verify(falsingCollector, never()).onScreenTurningOn()
-            verify(falsingCollector, never()).onScreenOnFromTouch()
-            verify(falsingCollector, never()).onScreenOff()
-
-            powerInteractor.setAsleepForTest()
-            runCurrent()
-            verify(falsingCollector, never()).onScreenTurningOn()
-            verify(falsingCollector, never()).onScreenOnFromTouch()
-            verify(falsingCollector, never()).onScreenOff()
-
-            powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_TAP)
-            runCurrent()
-            verify(falsingCollector, never()).onScreenTurningOn()
-            verify(falsingCollector, never()).onScreenOnFromTouch()
-            verify(falsingCollector, never()).onScreenOff()
-
-            powerInteractor.setAsleepForTest()
-            runCurrent()
-            verify(falsingCollector, never()).onScreenTurningOn()
-            verify(falsingCollector, never()).onScreenOnFromTouch()
-            verify(falsingCollector, never()).onScreenOff()
-
-            powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_POWER_BUTTON)
-            runCurrent()
-            verify(falsingCollector, never()).onScreenTurningOn()
-            verify(falsingCollector, never()).onScreenOnFromTouch()
-            verify(falsingCollector, never()).onScreenOff()
-        }
-
-    @Test
     fun collectFalsingSignals_bouncerVisibility() =
         testScope.runTest {
             prepareState(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt
index fd9f5f0..20dfd3e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt
@@ -18,6 +18,7 @@
 
 import android.view.Display
 import android.view.Display.TYPE_EXTERNAL
+import android.view.MotionEvent
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -28,11 +29,16 @@
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.shade.domain.interactor.notificationElement
+import com.android.systemui.shade.domain.interactor.qsElement
+import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlin.test.Test
 import kotlinx.coroutines.test.runTest
 import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -50,9 +56,19 @@
             keyguardRepository,
             testScope.backgroundScope,
             shadeOnDefaultDisplayWhenLocked = shadeOnDefaultDisplayWhenLocked,
+            shadeInteractor = { kosmos.shadeInteractor },
+            { kosmos.qsElement },
+            { kosmos.notificationElement },
         )
     }
 
+    private fun createMotionEventForDisplay(displayId: Int, xCoordinate: Float = 0f): MotionEvent {
+        return mock<MotionEvent> {
+            on { getX() } doReturn xCoordinate
+            on { getDisplayId() } doReturn displayId
+        }
+    }
+
     @Test
     fun displayId_defaultToDefaultDisplay() {
         val underTest = createUnderTest()
@@ -67,7 +83,7 @@
             val displayId by collectLastValue(underTest.displayId)
 
             displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL))
-            underTest.onStatusBarTouched(2)
+            underTest.onStatusBarTouched(createMotionEventForDisplay(2), STATUS_BAR_WIDTH)
 
             assertThat(displayId).isEqualTo(2)
         }
@@ -79,7 +95,7 @@
             val displayIds by collectValues(underTest.displayId)
             assertThat(displayIds).isEqualTo(listOf(Display.DEFAULT_DISPLAY))
 
-            underTest.onStatusBarTouched(2)
+            underTest.onStatusBarTouched(createMotionEventForDisplay(2), STATUS_BAR_WIDTH)
 
             // Never set, as 2 was not a display according to the repository.
             assertThat(displayIds).isEqualTo(listOf(Display.DEFAULT_DISPLAY))
@@ -92,7 +108,7 @@
             val displayId by collectLastValue(underTest.displayId)
 
             displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL))
-            underTest.onStatusBarTouched(2)
+            underTest.onStatusBarTouched(createMotionEventForDisplay(2), STATUS_BAR_WIDTH)
 
             assertThat(displayId).isEqualTo(2)
 
@@ -108,7 +124,7 @@
             val displayId by collectLastValue(underTest.displayId)
 
             displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL))
-            underTest.onStatusBarTouched(2)
+            underTest.onStatusBarTouched(createMotionEventForDisplay(2), STATUS_BAR_WIDTH)
 
             assertThat(displayId).isEqualTo(2)
 
@@ -124,7 +140,7 @@
             val displayId by collectLastValue(underTest.displayId)
 
             displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL))
-            underTest.onStatusBarTouched(2)
+            underTest.onStatusBarTouched(createMotionEventForDisplay(2), STATUS_BAR_WIDTH)
 
             assertThat(displayId).isEqualTo(2)
 
@@ -136,4 +152,48 @@
 
             assertThat(displayId).isEqualTo(2)
         }
+
+    @Test
+    fun onStatusBarTouched_leftSide_intentSetToNotifications() =
+        testScope.runTest {
+            val underTest = createUnderTest(shadeOnDefaultDisplayWhenLocked = true)
+
+            underTest.onStatusBarTouched(
+                createMotionEventForDisplay(2, STATUS_BAR_WIDTH * 0.1f),
+                STATUS_BAR_WIDTH,
+            )
+
+            assertThat(underTest.consumeExpansionIntent()).isEqualTo(kosmos.notificationElement)
+        }
+
+    @Test
+    fun onStatusBarTouched_rightSide_intentSetToQs() =
+        testScope.runTest {
+            val underTest = createUnderTest(shadeOnDefaultDisplayWhenLocked = true)
+
+            underTest.onStatusBarTouched(
+                createMotionEventForDisplay(2, STATUS_BAR_WIDTH * 0.95f),
+                STATUS_BAR_WIDTH,
+            )
+
+            assertThat(underTest.consumeExpansionIntent()).isEqualTo(kosmos.qsElement)
+        }
+
+    @Test
+    fun onStatusBarTouched_nullAfterConsumed() =
+        testScope.runTest {
+            val underTest = createUnderTest(shadeOnDefaultDisplayWhenLocked = true)
+
+            underTest.onStatusBarTouched(
+                createMotionEventForDisplay(2, STATUS_BAR_WIDTH * 0.1f),
+                STATUS_BAR_WIDTH,
+            )
+            assertThat(underTest.consumeExpansionIntent()).isEqualTo(kosmos.notificationElement)
+
+            assertThat(underTest.consumeExpansionIntent()).isNull()
+        }
+
+    companion object {
+        private const val STATUS_BAR_WIDTH = 100
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeExpandedStateInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeExpandedStateInteractorTest.kt
index 58396e7..8aa8a50 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeExpandedStateInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeExpandedStateInteractorTest.kt
@@ -22,8 +22,6 @@
 import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.kosmos.useUnconfinedTestDispatcher
-import com.android.systemui.shade.domain.interactor.ShadeExpandedStateInteractorImpl.NotificationElement
-import com.android.systemui.shade.domain.interactor.ShadeExpandedStateInteractorImpl.QSElement
 import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
@@ -52,7 +50,7 @@
 
             val element = currentlyExpandedElement.value
 
-            assertThat(element).isInstanceOf(QSElement::class.java)
+            assertThat(element).isInstanceOf(QSShadeElement::class.java)
         }
 
     @Test
@@ -62,7 +60,7 @@
 
             val element = underTest.currentlyExpandedElement.value
 
-            assertThat(element).isInstanceOf(NotificationElement::class.java)
+            assertThat(element).isInstanceOf(NotificationShadeElement::class.java)
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/customization/data/SensorLocationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/customization/data/SensorLocationTest.kt
new file mode 100644
index 0000000..526fd45
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/customization/data/SensorLocationTest.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.shared.customization.data
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SensorLocationTest : SysuiTestCase() {
+
+    @Test
+    fun encodeAndDecode() {
+        val sensorLocation = SensorLocation(640, 2068, 117, 0.75f)
+
+        assertThat(SensorLocation.decode(sensorLocation.encode())).isEqualTo(sensorLocation)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
index 1df2553..c3547bc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
@@ -74,7 +74,8 @@
         testScope.runTest {
             val radius = MutableStateFlow(32)
             val leftOffset = MutableStateFlow(0)
-            val shape by collectLastValue(scrollViewModel.shadeScrimShape(radius, leftOffset))
+            val shape by
+                collectLastValue(scrollViewModel.notificationScrimShape(radius, leftOffset))
 
             // When: receive scrim bounds
             placeholderViewModel.onScrimBoundsChanged(
@@ -87,7 +88,7 @@
                         bounds =
                             ShadeScrimBounds(left = 0f, top = 200f, right = 100f, bottom = 550f),
                         topRadius = 32,
-                        bottomRadius = 0
+                        bottomRadius = 0,
                     )
                 )
 
@@ -104,7 +105,7 @@
                         bounds =
                             ShadeScrimBounds(left = 10f, top = 200f, right = 100f, bottom = 550f),
                         topRadius = 24,
-                        bottomRadius = 0
+                        bottomRadius = 0,
                     )
                 )
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt
index 3d5d1ed..9a42f5b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt
@@ -54,7 +54,7 @@
     private val activeNotificationListRepository = kosmos.activeNotificationListRepository
     private val fakeSecureSettingsRepository = kosmos.fakeSecureSettingsRepository
 
-    private val underTest = kosmos.emptyShadeViewModel
+    private val underTest by lazy { kosmos.emptyShadeViewModel }
 
     companion object {
         @JvmStatic
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt
index 06a2c5a..66ccf18 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.shade.data.repository.shadeRepository
 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding
+import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.runTest
@@ -40,28 +41,39 @@
     private val underTest = kosmos.notificationStackAppearanceInteractor
 
     @Test
-    fun stackBounds() =
+    fun stackNotificationScrimBounds() =
         testScope.runTest {
-            val stackBounds by collectLastValue(underTest.shadeScrimBounds)
+            val stackBounds by collectLastValue(underTest.notificationShadeScrimBounds)
 
-            val bounds1 =
-                ShadeScrimBounds(
-                    top = 100f,
-                    bottom = 200f,
-                )
-            underTest.setShadeScrimBounds(bounds1)
+            val bounds1 = ShadeScrimBounds(top = 100f, bottom = 200f)
+            underTest.setNotificationShadeScrimBounds(bounds1)
             assertThat(stackBounds).isEqualTo(bounds1)
 
-            val bounds2 =
-                ShadeScrimBounds(
-                    top = 200f,
-                    bottom = 300f,
-                )
-            underTest.setShadeScrimBounds(bounds2)
+            val bounds2 = ShadeScrimBounds(top = 200f, bottom = 300f)
+            underTest.setNotificationShadeScrimBounds(bounds2)
             assertThat(stackBounds).isEqualTo(bounds2)
         }
 
     @Test
+    fun setQsPanelShape() =
+        testScope.runTest {
+            val actual by collectLastValue(underTest.qsPanelShape)
+
+            val expected1 =
+                ShadeScrimShape(
+                    bounds = ShadeScrimBounds(top = 0f, bottom = 100f),
+                    topRadius = 0,
+                    bottomRadius = 10,
+                )
+            underTest.setQsPanelShape(expected1)
+            assertThat(actual).isEqualTo(expected1)
+
+            val expected2 = expected1.copy(topRadius = 10)
+            underTest.setQsPanelShape(expected2)
+            assertThat(expected2).isEqualTo(actual)
+        }
+
+    @Test
     fun stackRounding() =
         testScope.runTest {
             val stackRounding by collectLastValue(underTest.shadeScrimRounding)
@@ -76,13 +88,17 @@
         }
 
     @Test(expected = IllegalStateException::class)
-    fun setStackBounds_withImproperBounds_throwsException() =
+    fun stackNotificationScrimBounds_withImproperBounds_throwsException() =
         testScope.runTest {
-            underTest.setShadeScrimBounds(
-                ShadeScrimBounds(
-                    top = 100f,
-                    bottom = 99f,
-                )
+            underTest.setNotificationShadeScrimBounds(ShadeScrimBounds(top = 100f, bottom = 99f))
+        }
+
+    @Test(expected = IllegalStateException::class)
+    fun setQsPanelShape_withImproperBounds_throwsException() =
+        testScope.runTest {
+            val invalidBounds = ShadeScrimBounds(top = 0f, bottom = -10f)
+            underTest.setQsPanelShape(
+                ShadeScrimShape(bounds = invalidBounds, topRadius = 10, bottomRadius = 10)
             )
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt
index 4944c8b..14e7cdc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt
@@ -38,12 +38,14 @@
     private val underTest by lazy { kosmos.notificationsPlaceholderViewModel }
 
     @Test
-    fun onBoundsChanged() =
+    fun onScrimBoundsChanged() =
         kosmos.testScope.runTest {
             val bounds = ShadeScrimBounds(left = 5f, top = 15f, right = 25f, bottom = 35f)
             underTest.onScrimBoundsChanged(bounds)
             val stackBounds by
-                collectLastValue(kosmos.notificationStackAppearanceInteractor.shadeScrimBounds)
+                collectLastValue(
+                    kosmos.notificationStackAppearanceInteractor.notificationShadeScrimBounds
+                )
             assertThat(stackBounds).isEqualTo(bounds)
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
index 6feada1..937f333 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/FakeHomeStatusBarViewModel.kt
@@ -46,6 +46,8 @@
 
     override val isHomeStatusBarAllowedByScene = MutableStateFlow(false)
 
+    override val shouldHomeStatusBarBeVisible = MutableStateFlow(false)
+
     override val shouldShowOperatorNameView = MutableStateFlow(false)
 
     override val isClockVisible =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
index e95bc33..be4af86 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModelImplTest.kt
@@ -575,6 +575,98 @@
         }
 
     @Test
+    fun shouldHomeStatusBarBeVisible_keyguardNotGone_noHun_false() =
+        kosmos.runTest {
+            // Do not transition from keyguard. i.e., we don't call transitionKeyguardToGone()
+
+            // Nothing disabled
+            fakeDisableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE)
+
+            val latest by collectLastValue(underTest.shouldHomeStatusBarBeVisible)
+            assertThat(latest).isFalse()
+        }
+
+    @Test
+    fun shouldHomeStatusBarBeVisible_keyguardNotGone_hun_true() =
+        kosmos.runTest {
+            // Keyguard gone
+            transitionKeyguardToGone()
+
+            // Nothing disabled
+            fakeDisableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE)
+
+            // there is an active HUN
+            headsUpNotificationRepository.setNotifications(
+                UnconfinedFakeHeadsUpRowRepository(
+                    key = "key",
+                    pinnedStatus = MutableStateFlow(PinnedStatus.PinnedByUser),
+                )
+            )
+
+            val latest by collectLastValue(underTest.shouldHomeStatusBarBeVisible)
+            assertThat(latest).isTrue()
+        }
+
+    @Test
+    fun shouldHomeStatusBarBeVisible_keyguardGone_noHun_notInCamera_true() =
+        kosmos.runTest {
+            // Keyguard gone
+            transitionKeyguardToGone()
+
+            // Nothing disabled
+            fakeDisableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE)
+
+            val latest by collectLastValue(underTest.shouldHomeStatusBarBeVisible)
+            assertThat(latest).isTrue()
+        }
+
+    @Test
+    fun shouldHomeStatusBarBeVisible_keyguardGone_hun_notInCamera_true() =
+        kosmos.runTest {
+            // Keyguard gone
+            transitionKeyguardToGone()
+
+            // Nothing disabled
+            fakeDisableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE)
+
+            // there is an active HUN
+            headsUpNotificationRepository.setNotifications(
+                UnconfinedFakeHeadsUpRowRepository(
+                    key = "key",
+                    pinnedStatus = MutableStateFlow(PinnedStatus.PinnedByUser),
+                )
+            )
+
+            val latest by collectLastValue(underTest.shouldHomeStatusBarBeVisible)
+            assertThat(latest).isTrue()
+        }
+
+    @Test
+    fun shouldHomeStatusBarBeVisible_keyguardGone_noHun_inCamera_false() =
+        kosmos.runTest {
+            // Keyguard gone
+            transitionKeyguardToGone()
+
+            // Nothing disabled
+            fakeDisableFlagsRepository.disableFlags.value =
+                DisableFlagsModel(DISABLE_NONE, DISABLE2_NONE)
+
+            fakeKeyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.OCCLUDED,
+                testScope = testScope,
+            )
+            kosmos.keyguardInteractor.onCameraLaunchDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP)
+
+            val latest by collectLastValue(underTest.shouldHomeStatusBarBeVisible)
+            assertThat(latest).isFalse()
+        }
+
+    @Test
     fun isClockVisible_allowedByDisableFlags_visible() =
         kosmos.runTest {
             val latest by collectLastValue(underTest.isClockVisible)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/StatusBarOperatorNameViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/StatusBarOperatorNameViewModelTest.kt
index 20cc85f..8608b0b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/StatusBarOperatorNameViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/StatusBarOperatorNameViewModelTest.kt
@@ -39,14 +39,12 @@
         kosmos.runTest {
             val intr1 = fakeMobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
             val intr2 = fakeMobileIconsInteractor.getMobileConnectionInteractorForSubId(2)
-            val invalidIntr = fakeMobileIconsInteractor.getMobileConnectionInteractorForSubId(-1)
 
             // GIVEN default data subId is 1
             fakeMobileIconsInteractor.defaultDataSubId.value = 1
 
             intr1.carrierName.value = "Test Name 1"
             intr2.carrierName.value = "Test Name 2"
-            invalidIntr.carrierName.value = "default network name"
 
             val latest by collectLastValue(underTest.operatorName)
 
@@ -56,8 +54,19 @@
 
             assertThat(latest).isEqualTo("Test Name 2")
 
-            fakeMobileIconsInteractor.defaultDataSubId.value = -1
+            fakeMobileIconsInteractor.defaultDataSubId.value = null
 
-            assertThat(latest).isEqualTo("default network name")
+            assertThat(latest).isNull()
+        }
+
+    @Test
+    fun operatorName_noDefaultDataSubId_null() =
+        kosmos.runTest {
+            // GIVEN defaultDataSubId is null
+            fakeMobileIconsInteractor.defaultDataSubId.value = null
+
+            val latest by collectLastValue(underTest.operatorName)
+
+            assertThat(latest).isNull()
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
index 7802b92..ff1ffcc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
@@ -36,8 +36,9 @@
 import com.android.settingslib.notification.modes.TestModeBuilder.MANUAL_DND
 import com.android.settingslib.volume.shared.model.AudioStream
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
 import com.android.systemui.shared.settings.data.repository.secureSettingsRepository
 import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
 import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
@@ -45,186 +46,151 @@
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import java.time.Duration
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 @SmallTest
 class ZenModeInteractorTest : SysuiTestCase() {
-    private val kosmos = testKosmos()
-    private val testScope = kosmos.testScope
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
     private val zenModeRepository = kosmos.fakeZenModeRepository
     private val settingsRepository = kosmos.secureSettingsRepository
     private val deviceProvisioningRepository = kosmos.fakeDeviceProvisioningRepository
 
-    private val underTest = kosmos.zenModeInteractor
+    private val underTest by lazy { kosmos.zenModeInteractor }
 
     @Test
     fun isZenAvailable_off() =
-        testScope.runTest {
+        kosmos.runTest {
             val isZenAvailable by collectLastValue(underTest.isZenAvailable)
             deviceProvisioningRepository.setDeviceProvisioned(false)
-            runCurrent()
-
             assertThat(isZenAvailable).isFalse()
         }
 
     @Test
     fun isZenAvailable_on() =
-        testScope.runTest {
+        kosmos.runTest {
             val isZenAvailable by collectLastValue(underTest.isZenAvailable)
             deviceProvisioningRepository.setDeviceProvisioned(true)
-            runCurrent()
-
             assertThat(isZenAvailable).isTrue()
         }
 
     @Test
     fun isZenModeEnabled_off() =
-        testScope.runTest {
+        kosmos.runTest {
             val enabled by collectLastValue(underTest.isZenModeEnabled)
-
             zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_OFF)
-            runCurrent()
-
             assertThat(enabled).isFalse()
         }
 
     @Test
     fun isZenModeEnabled_alarms() =
-        testScope.runTest {
+        kosmos.runTest {
             val enabled by collectLastValue(underTest.isZenModeEnabled)
-
             zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_ALARMS)
-            runCurrent()
-
             assertThat(enabled).isTrue()
         }
 
     @Test
     fun isZenModeEnabled_importantInterruptions() =
-        testScope.runTest {
+        kosmos.runTest {
             val enabled by collectLastValue(underTest.isZenModeEnabled)
-
             zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
-            runCurrent()
-
             assertThat(enabled).isTrue()
         }
 
     @Test
     fun isZenModeEnabled_noInterruptions() =
-        testScope.runTest {
+        kosmos.runTest {
             val enabled by collectLastValue(underTest.isZenModeEnabled)
-
             zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS)
-            runCurrent()
-
             assertThat(enabled).isTrue()
         }
 
     @Test
     fun testIsZenModeEnabled_unknown() =
-        testScope.runTest {
+        kosmos.runTest {
             val enabled by collectLastValue(underTest.isZenModeEnabled)
-
             // this should fail if we ever add another zen mode type
             zenModeRepository.updateZenMode(4)
-            runCurrent()
-
             assertThat(enabled).isFalse()
         }
 
     @Test
     fun areNotificationsHiddenInShade_noPolicy() =
-        testScope.runTest {
+        kosmos.runTest {
             val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
 
             zenModeRepository.updateNotificationPolicy(null)
             zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
-            runCurrent()
 
             assertThat(hidden).isFalse()
         }
 
     @Test
     fun areNotificationsHiddenInShade_zenOffShadeSuppressed() =
-        testScope.runTest {
+        kosmos.runTest {
             val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
 
             zenModeRepository.updateNotificationPolicy(
                 suppressedVisualEffects = Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST
             )
             zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_OFF)
-            runCurrent()
 
             assertThat(hidden).isFalse()
         }
 
     @Test
     fun areNotificationsHiddenInShade_zenOnShadeNotSuppressed() =
-        testScope.runTest {
+        kosmos.runTest {
             val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
 
             zenModeRepository.updateNotificationPolicy(
                 suppressedVisualEffects = Policy.SUPPRESSED_EFFECT_STATUS_BAR
             )
             zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
-            runCurrent()
 
             assertThat(hidden).isFalse()
         }
 
     @Test
     fun areNotificationsHiddenInShade_zenOnShadeSuppressed() =
-        testScope.runTest {
+        kosmos.runTest {
             val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
 
             zenModeRepository.updateNotificationPolicy(
                 suppressedVisualEffects = Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST
             )
             zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
-            runCurrent()
 
             assertThat(hidden).isTrue()
         }
 
     @Test
     fun shouldAskForZenDuration_falseForNonManualDnd() =
-        testScope.runTest {
+        kosmos.runTest {
             settingsRepository.setInt(ZEN_DURATION, ZEN_DURATION_PROMPT)
-            runCurrent()
-
             assertThat(underTest.shouldAskForZenDuration(TestModeBuilder.EXAMPLE)).isFalse()
         }
 
     @Test
     fun shouldAskForZenDuration_changesWithSetting() =
-        testScope.runTest {
-            val manualDnd = TestModeBuilder().makeManualDnd().setActive(true).build()
+        kosmos.runTest {
+            val manualDnd by collectLastValue(underTest.dndMode)
 
             settingsRepository.setInt(ZEN_DURATION, ZEN_DURATION_FOREVER)
-            runCurrent()
-
-            assertThat(underTest.shouldAskForZenDuration(manualDnd)).isFalse()
+            assertThat(underTest.shouldAskForZenDuration(manualDnd!!)).isFalse()
 
             settingsRepository.setInt(ZEN_DURATION, ZEN_DURATION_PROMPT)
-            runCurrent()
-
-            assertThat(underTest.shouldAskForZenDuration(manualDnd)).isTrue()
+            assertThat(underTest.shouldAskForZenDuration(manualDnd!!)).isTrue()
         }
 
     @Test
     fun activateMode_nonManualDnd() =
-        testScope.runTest {
+        kosmos.runTest {
             val mode = TestModeBuilder().setActive(false).build()
             zenModeRepository.addModes(listOf(mode))
             settingsRepository.setInt(ZEN_DURATION, 60)
-            runCurrent()
 
             underTest.activateMode(mode)
             assertThat(zenModeRepository.getMode(mode.id)?.isActive).isTrue()
@@ -233,16 +199,14 @@
 
     @Test
     fun activateMode_usesCorrectDuration() =
-        testScope.runTest {
+        kosmos.runTest {
             settingsRepository.setInt(ZEN_DURATION, ZEN_DURATION_FOREVER)
-            runCurrent()
 
             underTest.activateMode(MANUAL_DND)
             assertThat(zenModeRepository.getModeActiveDuration(MANUAL_DND.id)).isNull()
 
             zenModeRepository.deactivateMode(MANUAL_DND)
             settingsRepository.setInt(ZEN_DURATION, 60)
-            runCurrent()
 
             underTest.activateMode(MANUAL_DND)
             assertThat(zenModeRepository.getModeActiveDuration(MANUAL_DND.id))
@@ -251,7 +215,7 @@
 
     @Test
     fun deactivateAllModes_updatesCorrectModes() =
-        testScope.runTest {
+        kosmos.runTest {
             zenModeRepository.activateMode(MANUAL_DND)
             zenModeRepository.addModes(
                 listOf(
@@ -267,72 +231,69 @@
 
     @Test
     fun activeModes_computesMainActiveMode() =
-        testScope.runTest {
+        kosmos.runTest {
             val activeModes by collectLastValue(underTest.activeModes)
 
             zenModeRepository.addMode(id = "Bedtime", type = AutomaticZenRule.TYPE_BEDTIME)
             zenModeRepository.addMode(id = "Other", type = AutomaticZenRule.TYPE_OTHER)
-
-            runCurrent()
             assertThat(activeModes?.modeNames).hasSize(0)
             assertThat(activeModes?.mainMode).isNull()
 
             zenModeRepository.activateMode("Other")
-            runCurrent()
             assertThat(activeModes?.modeNames).containsExactly("Mode Other")
             assertThat(activeModes?.mainMode?.name).isEqualTo("Mode Other")
 
             zenModeRepository.activateMode("Bedtime")
-            runCurrent()
             assertThat(activeModes?.modeNames)
                 .containsExactly("Mode Bedtime", "Mode Other")
                 .inOrder()
             assertThat(activeModes?.mainMode?.name).isEqualTo("Mode Bedtime")
 
             zenModeRepository.deactivateMode("Other")
-            runCurrent()
             assertThat(activeModes?.modeNames).containsExactly("Mode Bedtime")
             assertThat(activeModes?.mainMode?.name).isEqualTo("Mode Bedtime")
 
             zenModeRepository.deactivateMode("Bedtime")
-            runCurrent()
             assertThat(activeModes?.modeNames).hasSize(0)
             assertThat(activeModes?.mainMode).isNull()
         }
 
     @Test
-    fun getActiveModes_computesMainActiveMode() = runTest {
-        zenModeRepository.addMode(id = "Bedtime", type = AutomaticZenRule.TYPE_BEDTIME)
-        zenModeRepository.addMode(id = "Other", type = AutomaticZenRule.TYPE_OTHER)
+    fun getActiveModes_computesMainActiveMode() =
+        kosmos.runTest {
+            zenModeRepository.addMode(id = "Bedtime", type = AutomaticZenRule.TYPE_BEDTIME)
+            zenModeRepository.addMode(id = "Other", type = AutomaticZenRule.TYPE_OTHER)
 
-        var activeModes = underTest.getActiveModes()
-        assertThat(activeModes.modeNames).hasSize(0)
-        assertThat(activeModes.mainMode).isNull()
+            var activeModes = underTest.getActiveModes()
+            assertThat(activeModes.modeNames).hasSize(0)
+            assertThat(activeModes.mainMode).isNull()
 
-        zenModeRepository.activateMode("Other")
-        activeModes = underTest.getActiveModes()
-        assertThat(activeModes.modeNames).containsExactly("Mode Other")
-        assertThat(activeModes.mainMode?.name).isEqualTo("Mode Other")
+            zenModeRepository.activateMode("Other")
+            activeModes = underTest.getActiveModes()
+            assertThat(activeModes.modeNames).containsExactly("Mode Other")
+            assertThat(activeModes.mainMode?.name).isEqualTo("Mode Other")
 
-        zenModeRepository.activateMode("Bedtime")
-        activeModes = underTest.getActiveModes()
-        assertThat(activeModes.modeNames).containsExactly("Mode Bedtime", "Mode Other").inOrder()
-        assertThat(activeModes.mainMode?.name).isEqualTo("Mode Bedtime")
+            zenModeRepository.activateMode("Bedtime")
+            activeModes = underTest.getActiveModes()
+            assertThat(activeModes.modeNames)
+                .containsExactly("Mode Bedtime", "Mode Other")
+                .inOrder()
+            assertThat(activeModes.mainMode?.name).isEqualTo("Mode Bedtime")
 
-        zenModeRepository.deactivateMode("Other")
-        activeModes = underTest.getActiveModes()
-        assertThat(activeModes.modeNames).containsExactly("Mode Bedtime")
-        assertThat(activeModes.mainMode?.name).isEqualTo("Mode Bedtime")
+            zenModeRepository.deactivateMode("Other")
+            activeModes = underTest.getActiveModes()
+            assertThat(activeModes.modeNames).containsExactly("Mode Bedtime")
+            assertThat(activeModes.mainMode?.name).isEqualTo("Mode Bedtime")
 
-        zenModeRepository.deactivateMode("Bedtime")
-        activeModes = underTest.getActiveModes()
-        assertThat(activeModes.modeNames).hasSize(0)
-        assertThat(activeModes.mainMode).isNull()
-    }
+            zenModeRepository.deactivateMode("Bedtime")
+            activeModes = underTest.getActiveModes()
+            assertThat(activeModes.modeNames).hasSize(0)
+            assertThat(activeModes.mainMode).isNull()
+        }
 
     @Test
     fun mainActiveMode_flows() =
-        testScope.runTest {
+        kosmos.runTest {
             val mainActiveMode by collectLastValue(underTest.mainActiveMode)
 
             zenModeRepository.addModes(
@@ -355,51 +316,42 @@
                         .build(),
                 )
             )
-
-            runCurrent()
             assertThat(mainActiveMode).isNull()
 
             zenModeRepository.activateMode("Other")
-            runCurrent()
             assertThat(mainActiveMode?.name).isEqualTo("Mode Other")
             assertThat(mainActiveMode?.icon?.key?.resId)
                 .isEqualTo(R.drawable.ic_zen_mode_type_other)
 
             zenModeRepository.activateMode("Bedtime")
-            runCurrent()
             assertThat(mainActiveMode?.name).isEqualTo("Mode Bedtime")
             assertThat(mainActiveMode?.icon?.key?.resId)
                 .isEqualTo(R.drawable.ic_zen_mode_type_bedtime)
 
             zenModeRepository.deactivateMode("Other")
-            runCurrent()
             assertThat(mainActiveMode?.name).isEqualTo("Mode Bedtime")
             assertThat(mainActiveMode?.icon?.key?.resId)
                 .isEqualTo(R.drawable.ic_zen_mode_type_bedtime)
 
             zenModeRepository.deactivateMode("Bedtime")
-            runCurrent()
             assertThat(mainActiveMode).isNull()
         }
 
     @Test
     @EnableFlags(Flags.FLAG_MODES_UI)
     fun dndMode_flows() =
-        testScope.runTest {
+        kosmos.runTest {
             val dndMode by collectLastValue(underTest.dndMode)
-
             assertThat(dndMode!!.isActive).isFalse()
 
             zenModeRepository.activateMode(MANUAL_DND)
-            runCurrent()
-
             assertThat(dndMode!!.isActive).isTrue()
         }
 
     @Test
     @EnableFlags(Flags.FLAG_MODES_UI)
     fun activeModesBlockingMedia_hasModesWithPolicyBlockingMedia() =
-        testScope.runTest {
+        kosmos.runTest {
             val blockingMedia by
                 collectLastValue(
                     underTest.activeModesBlockingStream(AudioStream(AudioManager.STREAM_MUSIC))
@@ -429,7 +381,6 @@
                         .build(),
                 )
             )
-            runCurrent()
 
             assertThat(blockingMedia!!.mainMode!!.name).isEqualTo("Blocks media, Active")
             assertThat(blockingMedia!!.modeNames)
@@ -440,7 +391,7 @@
     @Test
     @EnableFlags(Flags.FLAG_MODES_UI)
     fun activeModesBlockingAlarms_hasModesWithPolicyBlockingAlarms() =
-        testScope.runTest {
+        kosmos.runTest {
             val blockingAlarms by
                 collectLastValue(
                     underTest.activeModesBlockingStream(AudioStream(AudioManager.STREAM_ALARM))
@@ -470,7 +421,6 @@
                         .build(),
                 )
             )
-            runCurrent()
 
             assertThat(blockingAlarms!!.mainMode!!.name).isEqualTo("Blocks alarms, Active")
             assertThat(blockingAlarms!!.modeNames)
@@ -481,7 +431,7 @@
     @Test
     @EnableFlags(Flags.FLAG_MODES_UI)
     fun activeModesBlockingAlarms_hasModesWithPolicyBlockingSystem() =
-        testScope.runTest {
+        kosmos.runTest {
             val blockingSystem by
                 collectLastValue(
                     underTest.activeModesBlockingStream(AudioStream(AudioManager.STREAM_SYSTEM))
@@ -511,7 +461,6 @@
                         .build(),
                 )
             )
-            runCurrent()
 
             assertThat(blockingSystem!!.mainMode!!.name).isEqualTo("Blocks system, Active")
             assertThat(blockingSystem!!.modeNames)
@@ -522,7 +471,7 @@
     @Test
     @EnableFlags(ModesEmptyShadeFix.FLAG_NAME, Flags.FLAG_MODES_UI, Flags.FLAG_MODES_API)
     fun modesHidingNotifications_onlyIncludesModesWithNotifListSuppression() =
-        testScope.runTest {
+        kosmos.runTest {
             val modesHidingNotifications by collectLastValue(underTest.modesHidingNotifications)
 
             zenModeRepository.addModes(
@@ -554,7 +503,6 @@
                         .build(),
                 )
             )
-            runCurrent()
 
             assertThat(modesHidingNotifications?.map { it.name })
                 .containsExactly("Has list suppression 1", "Has list suppression 2")
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialActionStateSaverTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialActionStateSaverTest.kt
new file mode 100644
index 0000000..4cbe33d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialActionStateSaverTest.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.touchpad.tutorial.ui.composable
+
+import androidx.compose.runtime.saveable.SaverScope
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Error
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Finished
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgress
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgressAfterError
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.NotStarted
+import com.android.systemui.res.R
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TutorialActionStateSaverTest : SysuiTestCase() {
+
+    private val saver = TutorialActionState.stateSaver()
+    private val saverScope: SaverScope =
+        object : SaverScope {
+            override fun canBeSaved(value: Any) = true
+        }
+
+    @Test
+    fun inProgressIsRestoredToNotStartedState() {
+        assertRestoredState(
+            savedState = InProgress(progress = 0f),
+            expectedRestoredState = NotStarted,
+        )
+    }
+
+    @Test
+    fun inProgressErrorIsRestoredToErrorState() {
+        assertRestoredState(
+            savedState = InProgressAfterError(InProgress(progress = 0f)),
+            expectedRestoredState = Error,
+        )
+    }
+
+    @Test
+    fun otherStatesAreRestoredToTheSameState() {
+        assertRestoredState(savedState = NotStarted, expectedRestoredState = NotStarted)
+        assertRestoredState(savedState = Error, expectedRestoredState = Error)
+        assertRestoredState(
+            savedState = Finished(successAnimation = R.raw.trackpad_home_success),
+            expectedRestoredState = Finished(successAnimation = R.raw.trackpad_home_success),
+        )
+    }
+
+    private fun assertRestoredState(
+        savedState: TutorialActionState,
+        expectedRestoredState: TutorialActionState,
+    ) {
+        val savedValue = with(saver) { saverScope.save(savedState) }
+        assertThat(saver.restore(savedValue!!)).isEqualTo(expectedRestoredState)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelTest.kt
index d8184db..2dcfdd9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelTest.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.plugins.fakeVolumeDialogController
 import com.android.systemui.testKosmos
+import com.android.systemui.util.time.fakeSystemClock
 import com.android.systemui.volume.data.repository.audioSystemRepository
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -78,7 +79,7 @@
             val normalRingerMode = RingerMode(RINGER_MODE_NORMAL)
 
             setUpRingerModeAndOpenDrawer(normalRingerMode)
-            underTest.onRingerButtonClicked(normalRingerMode)
+            onRingerButtonClicked(normalRingerMode)
             controller.getState()
 
             assertThat(ringerViewModel).isInstanceOf(RingerViewModelState.Available::class.java)
@@ -95,7 +96,7 @@
 
             setUpRingerModeAndOpenDrawer(normalRingerMode)
             // Select vibrate ringer mode.
-            underTest.onRingerButtonClicked(vibrateRingerMode)
+            onRingerButtonClicked(vibrateRingerMode)
             controller.getState()
             runCurrent()
 
@@ -109,11 +110,11 @@
 
             val silentRingerMode = RingerMode(RINGER_MODE_SILENT)
             // Open drawer
-            underTest.onRingerButtonClicked(vibrateRingerMode)
+            onRingerButtonClicked(vibrateRingerMode)
             controller.getState()
 
             // Select silent ringer mode.
-            underTest.onRingerButtonClicked(silentRingerMode)
+            onRingerButtonClicked(silentRingerMode)
             controller.getState()
             runCurrent()
 
@@ -152,11 +153,16 @@
 
     private fun TestScope.setUpRingerModeAndOpenDrawer(selectedRingerMode: RingerMode) {
         setUpRingerMode(selectedRingerMode)
-        underTest.onRingerButtonClicked(RingerMode(selectedRingerMode.value))
+        onRingerButtonClicked(selectedRingerMode)
         controller.getState()
         runCurrent()
     }
 
+    private fun TestScope.onRingerButtonClicked(ringerMode: RingerMode) {
+        kosmos.fakeSystemClock.advanceTime(400L)
+        underTest.onRingerButtonClicked(ringerMode)
+    }
+
     private fun TestScope.setUpRingerMode(selectedRingerMode: RingerMode) {
         controller.setStreamVolume(STREAM_RING, 50)
         controller.setRingerMode(selectedRingerMode.value, false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt
new file mode 100644
index 0000000..ba6ea9f
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/GradientColorWallpaperTest.kt
@@ -0,0 +1,85 @@
+/*
+ * 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.wallpapers
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.graphics.Rect
+import android.graphics.RectF
+import android.service.wallpaper.WallpaperService.Engine
+import android.testing.TestableLooper.RunWithLooper
+import android.view.Surface
+import android.view.SurfaceHolder
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.spy
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@RunWithLooper
+class GradientColorWallpaperTest : SysuiTestCase() {
+
+    @Mock private lateinit var surfaceHolder: SurfaceHolder
+
+    @Mock private lateinit var surface: Surface
+
+    @Mock private lateinit var canvas: Canvas
+
+    @Mock private lateinit var mockContext: Context
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        whenever(surfaceHolder.surface).thenReturn(surface)
+        whenever(surfaceHolder.surfaceFrame).thenReturn(surfaceFrame)
+        whenever(surface.lockHardwareCanvas()).thenReturn(canvas)
+        whenever(mockContext.getColor(anyInt())).thenReturn(1)
+    }
+
+    private fun createGradientColorWallpaperEngine(): Engine {
+        val gradientColorWallpaper = GradientColorWallpaper()
+        val engine = spy(gradientColorWallpaper.onCreateEngine())
+        whenever(engine.displayContext).thenReturn(mockContext)
+        return engine
+    }
+
+    @Test
+    fun onSurfaceRedrawNeeded_shouldDrawInCanvas() {
+        val engine = createGradientColorWallpaperEngine()
+        engine.onCreate(surfaceHolder)
+
+        engine.onSurfaceRedrawNeeded(surfaceHolder)
+
+        verify(canvas).drawRect(any<RectF>(), any<Paint>())
+    }
+
+    private companion object {
+        val surfaceFrame = Rect(0, 0, 100, 100)
+    }
+}
diff --git a/packages/SystemUI/res/drawable/android16_patch_adaptive.xml b/packages/SystemUI/res/drawable/android16_patch_adaptive.xml
new file mode 100644
index 0000000..277df47
--- /dev/null
+++ b/packages/SystemUI/res/drawable/android16_patch_adaptive.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (C) 2024 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/android16_patch_adaptive_background"/>
+    <foreground android:drawable="@drawable/android16_patch_adaptive_foreground"/>
+    <monochrome android:drawable="@drawable/android16_patch_monochrome"/>
+</adaptive-icon>
diff --git a/packages/SystemUI/res/drawable/android16_patch_adaptive_background.xml b/packages/SystemUI/res/drawable/android16_patch_adaptive_background.xml
new file mode 100644
index 0000000..17c2b92
--- /dev/null
+++ b/packages/SystemUI/res/drawable/android16_patch_adaptive_background.xml
@@ -0,0 +1,245 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (C) 2024 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+  <group>
+    <clip-path
+        android:pathData="M0,0h108v108h-108z"/>
+    <path
+        android:pathData="M73,54L54,35L35,54L54,73L73,54Z"
+        android:fillColor="#34A853"/>
+    <path
+        android:pathData="M44.5,44.5L54,44.5L44.5,54L44.5,44.5Z"
+        android:fillColor="#1F8E3D"/>
+    <path
+        android:pathData="M63.5,63.5L54,63.5L63.5,54L63.5,63.5Z"
+        android:fillColor="#1F8E3D"/>
+    <path
+        android:pathData="M54,54L54,44.5L63.5,54L54,54Z"
+        android:fillColor="#1F8E3D"/>
+    <path
+        android:pathData="M54,44.5L54,35L63.5,44.5L54,44.5Z"
+        android:fillColor="#1F8E3D"/>
+    <path
+        android:pathData="M54,63.5L54,73L44.5,63.5L54,63.5Z"
+        android:fillColor="#1F8E3D"/>
+    <path
+        android:pathData="M63.5,54L63.5,44.5L73,54L63.5,54Z"
+        android:fillColor="#1F8E3D"/>
+    <path
+        android:pathData="M44.5,54L44.5,63.5L35,54L44.5,54Z"
+        android:fillColor="#1F8E3D"/>
+    <path
+        android:pathData="M54,54L54,63.5L44.5,54L54,54Z"
+        android:fillColor="#1F8E3D"/>
+    <path
+        android:pathData="M82.5,25.5L82.5,35L73,25.5L82.5,25.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M63.5,44.5L63.5,35L73,44.5L63.5,44.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M73,35L82.5,35L73,44.5L73,35Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M82.5,35L92,35L82.5,44.5L82.5,35Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M63.5,35L54,35L63.5,25.5L63.5,35Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M73,44.5L82.5,44.5L73,54L73,44.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M73,25.5L63.5,25.5L73,16L73,25.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M73,35L63.5,35L73,25.5L73,35Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M82.5,63.5L82.5,73L73,63.5L82.5,63.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M63.5,82.5L63.5,73L73,82.5L63.5,82.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M73,73L82.5,73L73,82.5L73,73Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M82.5,73L92,73L82.5,82.5L82.5,73Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M63.5,73L54,73L63.5,63.5L63.5,73Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M73,82.5L82.5,82.5L73,92L73,82.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M73,63.5L63.5,63.5L73,54L73,63.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M73,73L63.5,73L73,63.5L73,73Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M44.5,63.5L44.5,73L35,63.5L44.5,63.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M25.5,82.5L25.5,73L35,82.5L25.5,82.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M35,73L44.5,73L35,82.5L35,73Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M44.5,73L54,73L44.5,82.5L44.5,73Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M25.5,73L16,73L25.5,63.5L25.5,73Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M35,82.5L44.5,82.5L35,92L35,82.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M35,63.5L25.5,63.5L35,54L35,63.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M35,73L25.5,73L35,63.5L35,73Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M44.5,25.5L44.5,35L35,25.5L44.5,25.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M25.5,44.5L25.5,35L35,44.5L25.5,44.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M35,35L44.5,35L35,44.5L35,35Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M44.5,35L54,35L44.5,44.5L44.5,35Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M25.5,35L16,35L25.5,25.5L25.5,35Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M35,44.5L44.5,44.5L35,54L35,44.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M35,25.5L25.5,25.5L35,16L35,25.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M35,35L25.5,35L35,25.5L35,35Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M63.5,25.5L54,25.5L63.5,16L63.5,25.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M44.5,6.5L54,6.5L44.5,16L44.5,6.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M54,16L54,25.5L44.5,16L54,16Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M54,25.5L54,35L44.5,25.5L54,25.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M54,6.5L54,-3L63.5,6.5L54,6.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M44.5,16L44.5,25.5L35,16L44.5,16Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M63.5,16L63.5,6.5L73,16L63.5,16Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M54,16L54,6.5L63.5,16L54,16Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M101.5,63.5L92,63.5L101.5,54L101.5,63.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M82.5,44.5L92,44.5L82.5,54L82.5,44.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M92,54L92,63.5L82.5,54L92,54Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M92,63.5L92,73L82.5,63.5L92,63.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M92,44.5L92,35L101.5,44.5L92,44.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M82.5,54L82.5,63.5L73,54L82.5,54Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M101.5,54L101.5,44.5L111,54L101.5,54Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M92,54L92,44.5L101.5,54L92,54Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M63.5,101.5L54,101.5L63.5,92L63.5,101.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M44.5,82.5L54,82.5L44.5,92L44.5,82.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M54,92L54,101.5L44.5,92L54,92Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M54,101.5L54,111L44.5,101.5L54,101.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M54,82.5L54,73L63.5,82.5L54,82.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M44.5,92L44.5,101.5L35,92L44.5,92Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M63.5,92L63.5,82.5L73,92L63.5,92Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M54,92L54,82.5L63.5,92L54,92Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M25.5,63.5L16,63.5L25.5,54L25.5,63.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M6.5,44.5L16,44.5L6.5,54L6.5,44.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M16,54L16,63.5L6.5,54L16,54Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M16,63.5L16,73L6.5,63.5L16,63.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M16,44.5L16,35L25.5,44.5L16,44.5Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M6.5,54L6.5,63.5L-3,54L6.5,54Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M25.5,54L25.5,44.5L35,54L25.5,54Z"
+        android:fillColor="#16161D"/>
+    <path
+        android:pathData="M16,54L16,44.5L25.5,54L16,54Z"
+        android:fillColor="#16161D"/>
+  </group>
+</vector>
diff --git a/packages/SystemUI/res/drawable/android16_patch_adaptive_foreground.xml b/packages/SystemUI/res/drawable/android16_patch_adaptive_foreground.xml
new file mode 100644
index 0000000..4c29323
--- /dev/null
+++ b/packages/SystemUI/res/drawable/android16_patch_adaptive_foreground.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (C) 2024 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+  <path
+      android:pathData="M40.65,63.013C40.722,62.922 40.716,62.789 40.633,62.707V62.707C40.537,62.61 40.377,62.62 40.292,62.727C34.567,69.881 31.569,75.536 33.089,77.056C35.366,79.333 46.923,71.469 58.901,59.491C60.049,58.343 61.159,57.199 62.226,56.066C62.342,55.943 62.339,55.751 62.219,55.632L61.566,54.978C61.441,54.854 61.238,54.857 61.117,54.985C60.057,56.11 58.951,57.25 57.806,58.395C46.882,69.319 36.496,76.646 34.61,74.759C33.417,73.567 35.903,68.982 40.65,63.013Z"
+      android:fillColor="#C6FF00"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M67.956,52.033C68.205,51.966 68.462,52.115 68.529,52.364C68.596,52.614 68.448,52.871 68.198,52.938L67.956,52.033ZM68.198,52.938L63.926,54.083L63.683,53.178L67.956,52.033L68.198,52.938Z"
+      android:fillColor="#000000"/>
+  <path
+      android:pathData="M64.497,49.237C64.564,48.987 64.821,48.839 65.071,48.906C65.32,48.973 65.469,49.229 65.402,49.479L64.497,49.237ZM65.402,49.479L64.257,53.752L63.352,53.509L64.497,49.237L65.402,49.479Z"
+      android:fillColor="#000000"/>
+  <path
+      android:pathData="M66.145,51.236C64.869,49.961 62.83,49.931 61.591,51.17L58.825,53.937C58.585,54.176 58.585,54.564 58.825,54.803C59.063,55.042 59.452,55.042 59.691,54.803L60.436,54.057C60.915,53.579 61.69,53.579 62.169,54.057L63.324,55.212C63.802,55.691 63.802,56.466 63.324,56.945L62.578,57.69C62.339,57.929 62.339,58.318 62.578,58.557C62.817,58.796 63.205,58.796 63.444,58.557L66.211,55.79C67.45,54.551 67.42,52.512 66.145,51.236Z"
+      android:fillColor="#000000"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/android16_patch_monochrome.xml b/packages/SystemUI/res/drawable/android16_patch_monochrome.xml
new file mode 100644
index 0000000..608d5ea
--- /dev/null
+++ b/packages/SystemUI/res/drawable/android16_patch_monochrome.xml
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (C) 2024 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+  <path
+      android:strokeWidth="1"
+      android:pathData="M54.707,35.707L72.293,53.293A1,1 102.155,0 1,72.293 54.707L54.707,72.293A1,1 0,0 1,53.293 72.293L35.707,54.707A1,1 0,0 1,35.707 53.293L53.293,35.707A1,1 0,0 1,54.707 35.707z"
+      android:fillColor="#00000000"
+      android:strokeColor="#ffffff"/>
+  <path
+      android:pathData="M55.237,35.177L72.823,52.763A1.75,1.75 67.835,0 1,72.823 55.237L55.237,72.823A1.75,1.75 77.684,0 1,52.763 72.823L35.177,55.237A1.75,1.75 0,0 1,35.177 52.763L52.763,35.177A1.75,1.75 0,0 1,55.237 35.177z"
+      android:strokeWidth="1.5"
+      android:fillColor="#00000000"
+      android:strokeColor="#ffffff"/>
+  <path
+      android:pathData="M44.5,44.5h19v19h-19z"
+      android:strokeWidth="0.75"
+      android:fillColor="#00000000"
+      android:strokeColor="#ffffff"/>
+  <path
+      android:pathData="M54,44.5l9.5,9.5l-9.5,9.5l-9.5,-9.5z"
+      android:strokeWidth="0.75"
+      android:fillColor="#00000000"
+      android:strokeColor="#ffffff"/>
+  <path
+      android:pathData="M54,35V73"
+      android:strokeWidth="0.75"
+      android:fillColor="#00000000"
+      android:strokeColor="#ffffff"/>
+  <path
+      android:pathData="M73,54L35,54"
+      android:strokeWidth="0.75"
+      android:fillColor="#00000000"
+      android:strokeColor="#ffffff"/>
+  <path
+      android:pathData="M33.576,31.135l1.146,1.146l-1.146,1.146l-1.146,-1.146z"
+      android:fillColor="#E8F5E9"/>
+  <path
+      android:pathData="M31.146,65.966l1.146,1.146l-1.146,1.146l-1.146,-1.146z"
+      android:fillColor="#E8F5E9"/>
+  <path
+      android:pathData="M26.718,56l1.718,1.718l-1.718,1.718l-1.718,-1.718z"
+      android:fillColor="#E8F5E9"/>
+  <path
+      android:pathData="M31.146,48l1.146,1.146l-1.146,1.146l-1.146,-1.146z"
+      android:fillColor="#E8F5E9"/>
+  <path
+      android:pathData="M41.925,34.374l1.718,1.718l-1.718,1.718l-1.718,-1.718z"
+      android:fillColor="#E8F5E9"/>
+  <path
+      android:pathData="M63.146,71l1.146,1.146l-1.146,1.146l-1.146,-1.146z"
+      android:fillColor="#E8F5E9"/>
+  <path
+      android:pathData="M48.567,74.553l1.718,1.718l-1.718,1.718l-1.718,-1.718z"
+      android:fillColor="#E8F5E9"/>
+  <path
+      android:pathData="M51.146,26l1.146,1.146l-1.146,1.146l-1.146,-1.146z"
+      android:fillColor="#E8F5E9"/>
+  <path
+      android:pathData="M72.291,32.146l-1.146,1.146l-1.146,-1.146l1.146,-1.146z"
+      android:fillColor="#E8F5E9"/>
+  <path
+      android:pathData="M76.531,36.417l-1.718,1.718l-1.718,-1.718l1.718,-1.718z"
+      android:fillColor="#E8F5E9"/>
+  <path
+      android:pathData="M58.291,32.146l-1.146,1.146l-1.146,-1.146l1.146,-1.146z"
+      android:fillColor="#E8F5E9"/>
+  <path
+      android:pathData="M68.419,36.978l-1.146,1.146l-1.146,-1.146l1.146,-1.146z"
+      android:fillColor="#E8F5E9"/>
+  <path
+      android:pathData="M74.252,64.034l-1.146,1.146l-1.146,-1.146l1.146,-1.146z"
+      android:fillColor="#E8F5E9"/>
+  <path
+      android:pathData="M71.437,76.718l-1.718,1.718l-1.718,-1.718l1.718,-1.718z"
+      android:fillColor="#E8F5E9"/>
+  <path
+      android:pathData="M42.984,69.38l-1.146,1.146l-1.146,-1.146l1.146,-1.146z"
+      android:fillColor="#E8F5E9"/>
+  <path
+      android:pathData="M82.437,51.718l-1.718,1.718l-1.718,-1.718l1.718,-1.718z"
+      android:fillColor="#E8F5E9"/>
+  <path
+      android:pathData="M40.65,63.013C40.722,62.922 40.716,62.789 40.633,62.707V62.707C40.537,62.61 40.377,62.62 40.292,62.727C34.567,69.881 31.569,75.536 33.089,77.056C35.366,79.333 46.923,71.469 58.901,59.491C60.049,58.343 61.159,57.199 62.226,56.066C62.342,55.943 62.339,55.751 62.219,55.632L61.566,54.978C61.441,54.854 61.238,54.857 61.117,54.985C60.057,56.11 58.951,57.25 57.806,58.395C46.882,69.319 36.496,76.646 34.61,74.759C33.417,73.567 35.903,68.982 40.65,63.013Z"
+      android:fillColor="#ffffff"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M67.956,52.033C68.205,51.966 68.462,52.115 68.529,52.364C68.596,52.614 68.448,52.871 68.198,52.938L67.956,52.033ZM68.198,52.938L63.926,54.083L63.683,53.178L67.956,52.033L68.198,52.938Z"
+      android:fillColor="#ffffff"/>
+  <path
+      android:pathData="M64.497,49.237C64.564,48.987 64.821,48.839 65.071,48.906C65.32,48.972 65.469,49.229 65.402,49.479L64.497,49.237ZM65.402,49.479L64.257,53.752L63.352,53.509L64.497,49.237L65.402,49.479Z"
+      android:fillColor="#ffffff"/>
+  <path
+      android:pathData="M66.145,51.236C64.869,49.961 62.83,49.931 61.591,51.17L58.825,53.937C58.585,54.176 58.585,54.564 58.825,54.803C59.063,55.042 59.452,55.042 59.691,54.803L60.436,54.057C60.915,53.579 61.69,53.579 62.169,54.057L63.324,55.212C63.802,55.691 63.802,56.466 63.324,56.945L62.578,57.69C62.339,57.929 62.339,58.318 62.578,58.556C62.817,58.796 63.205,58.796 63.444,58.556L66.211,55.79C67.45,54.551 67.42,52.512 66.145,51.236Z"
+      android:fillColor="#ffffff"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index b3f32a2..5e8a8a5 100644
--- a/packages/SystemUI/res/layout/qs_footer_impl.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -34,13 +34,14 @@
             android:layout_height="match_parent"
             android:layout_gravity="center_vertical">
 
-            <TextView
+            <com.android.systemui.qs.BuildTextView
                 android:id="@+id/build"
                 android:layout_width="0dp"
                 android:layout_height="match_parent"
                 android:paddingEnd="4dp"
                 android:layout_weight="1"
-                android:clickable="true"
+                android:marqueeRepeatLimit="1"
+                android:clickable="false"
                 android:ellipsize="marquee"
                 android:focusable="true"
                 android:gravity="center_vertical"
diff --git a/packages/SystemUI/res/layout/volume_dialog_slider.xml b/packages/SystemUI/res/layout/volume_dialog_slider.xml
index 6eb7b73..c5f468e 100644
--- a/packages/SystemUI/res/layout/volume_dialog_slider.xml
+++ b/packages/SystemUI/res/layout/volume_dialog_slider.xml
@@ -14,8 +14,8 @@
      limitations under the License.
 -->
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="0dp"
-    android:layout_height="0dp"
+    android:layout_width="@dimen/volume_dialog_slider_width"
+    android:layout_height="match_parent"
     android:maxHeight="@dimen/volume_dialog_slider_height">
 
     <com.google.android.material.slider.Slider
diff --git a/packages/SystemUI/res/layout/window_magnification_settings_view.xml b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
index afd4fa7..7f735047 100644
--- a/packages/SystemUI/res/layout/window_magnification_settings_view.xml
+++ b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
@@ -132,7 +132,8 @@
             android:layout_height="wrap_content"
             android:track="@drawable/settingslib_track_selector"
             android:thumb="@drawable/settingslib_thumb_selector"
-            android:theme="@style/MainSwitch.Settingslib"/>
+            android:theme="@style/MainSwitch.Settingslib"
+            android:minHeight="48dp" />
 
     </LinearLayout>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index a8ee609..e785af8 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3815,7 +3815,7 @@
          user what action they need to take in the customization dialog to assign a new custom shortcut.
          The shortcut customize dialog allows users to add/remove custom shortcuts
          [CHAR LIMIT=NONE] -->
-    <string name="shortcut_customize_mode_add_shortcut_description">Press key to assign shortcut</string>
+    <string name="shortcut_customize_mode_add_shortcut_description">To create this shortcut, press the Action key and one or more other keys together</string>
     <!-- Sub title at the top of the remove custom shortcut dialog. Explains to the user what action
          they're about to take when they click remove shortcut. The shortcut customize dialog allows
          users to add/remove custom shortcuts
@@ -3912,7 +3912,7 @@
     <!-- Error message displayed when the user select a key combination that is already in use while
          assigning a new custom key combination to a shortcut in shortcut helper. The helper is a
          component that shows the user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] -->
-    <string name="shortcut_customizer_key_combination_in_use_error_message">Key combination already in use. Try another key.</string>
+    <string name="shortcut_customizer_key_combination_in_use_error_message">Key combination already in use. Try another combination.</string>
     <!-- Generic error message displayed when the user selected key combination cannot be used as
          custom keyboard shortcut in shortcut helper. The helper is a component that shows the user
          which keyboard shortcuts they can use and allows users to customize their keyboard
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginEnabler.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginEnabler.java
index 1c5da82..3d2ce42 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginEnabler.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginEnabler.java
@@ -27,9 +27,10 @@
     int DISABLED_INVALID_VERSION = 2;
     int DISABLED_FROM_EXPLICIT_CRASH = 3;
     int DISABLED_FROM_SYSTEM_CRASH = 4;
+    int DISABLED_UNKNOWN = 100;
 
     @IntDef({ENABLED, DISABLED_MANUALLY, DISABLED_INVALID_VERSION, DISABLED_FROM_EXPLICIT_CRASH,
-            DISABLED_FROM_SYSTEM_CRASH})
+            DISABLED_FROM_SYSTEM_CRASH, DISABLED_UNKNOWN})
     @interface DisableReason {
     }
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
index ff6bcdb..51892aa 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
@@ -19,7 +19,6 @@
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.view.RemoteAnimationTarget;
-import android.window.TransitionInfo;
 
 import com.android.systemui.shared.recents.model.ThumbnailData;
 
@@ -31,7 +30,7 @@
      */
     void onAnimationStart(RecentsAnimationControllerCompat controller,
             RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
-            Rect homeContentInsets, Rect minimizedHomeBounds, Bundle extras, TransitionInfo info);
+            Rect homeContentInsets, Rect minimizedHomeBounds, Bundle extras);
 
     /**
      * Called when the animation into Recents was canceled. This call is made on the binder thread.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index ff78848..ec97b8a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -208,7 +208,6 @@
         private final InputMethodManager mInputMethodManager;
         private final DelayableExecutor mMainExecutor;
         private final Resources mResources;
-        private final LiftToActivateListener mLiftToActivateListener;
         private final TelephonyManager mTelephonyManager;
         private final EmergencyButtonController.Factory mEmergencyButtonControllerFactory;
         private final FalsingCollector mFalsingCollector;
@@ -227,7 +226,7 @@
                 LatencyTracker latencyTracker,
                 KeyguardMessageAreaController.Factory messageAreaControllerFactory,
                 InputMethodManager inputMethodManager, @Main DelayableExecutor mainExecutor,
-                @Main Resources resources, LiftToActivateListener liftToActivateListener,
+                @Main Resources resources,
                 TelephonyManager telephonyManager, FalsingCollector falsingCollector,
                 EmergencyButtonController.Factory emergencyButtonControllerFactory,
                 DevicePostureController devicePostureController,
@@ -244,7 +243,6 @@
             mInputMethodManager = inputMethodManager;
             mMainExecutor = mainExecutor;
             mResources = resources;
-            mLiftToActivateListener = liftToActivateListener;
             mTelephonyManager = telephonyManager;
             mEmergencyButtonControllerFactory = emergencyButtonControllerFactory;
             mFalsingCollector = falsingCollector;
@@ -284,7 +282,7 @@
                 return new KeyguardPinViewController((KeyguardPINView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
-                        mLiftToActivateListener, emergencyButtonController, mFalsingCollector,
+                        emergencyButtonController, mFalsingCollector,
                         mDevicePostureController, mFeatureFlags, mSelectedUserInteractor,
                         mUiEventLogger, mKeyguardKeyboardInteractor, mBouncerHapticPlayer,
                         mUserActivityNotifier);
@@ -292,14 +290,14 @@
                 return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
-                        mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
+                        mTelephonyManager, mFalsingCollector,
                         emergencyButtonController, mFeatureFlags, mSelectedUserInteractor,
                         mKeyguardKeyboardInteractor, mBouncerHapticPlayer, mUserActivityNotifier);
             } else if (keyguardInputView instanceof KeyguardSimPukView) {
                 return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
-                        mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
+                        mTelephonyManager, mFalsingCollector,
                         emergencyButtonController, mFeatureFlags, mSelectedUserInteractor,
                         mKeyguardKeyboardInteractor, mBouncerHapticPlayer, mUserActivityNotifier
                 );
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index e22736b..622b67f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -297,13 +297,10 @@
         DisappearAnimationUtils disappearAnimationUtils = needsSlowUnlockTransition
                         ? mDisappearAnimationUtilsLocked
                         : mDisappearAnimationUtils;
-        android.util.Log.i("KeyguardPINView", "startDisappearAnimation: " + finishRunnable);
         disappearAnimationUtils.createAnimation(
                 this, 0, 200, mDisappearYTranslation, false,
                 mDisappearAnimationUtils.getInterpolator(), () -> {
                     if (finishRunnable != null) {
-                        android.util.Log.i("KeyguardPINView",
-                                "startDisappearAnimation, invoking run()");
                         finishRunnable.run();
                     }
                 },
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index d999994..7f176de 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -34,7 +34,6 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor;
-import com.android.systemui.Flags;
 import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
@@ -44,7 +43,6 @@
 public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinBasedInputView>
         extends KeyguardAbsKeyInputViewController<T> {
 
-    private final LiftToActivateListener mLiftToActivateListener;
     private final FalsingCollector mFalsingCollector;
     private final KeyguardKeyboardInteractor mKeyguardKeyboardInteractor;
     protected PasswordTextView mPasswordEntry;
@@ -73,7 +71,6 @@
             KeyguardSecurityCallback keyguardSecurityCallback,
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
             LatencyTracker latencyTracker,
-            LiftToActivateListener liftToActivateListener,
             EmergencyButtonController emergencyButtonController,
             FalsingCollector falsingCollector,
             FeatureFlags featureFlags,
@@ -85,7 +82,6 @@
                 messageAreaControllerFactory, latencyTracker, falsingCollector,
                 emergencyButtonController, featureFlags, selectedUserInteractor,
                 bouncerHapticPlayer, userActivityNotifier);
-        mLiftToActivateListener = liftToActivateListener;
         mFalsingCollector = falsingCollector;
         mKeyguardKeyboardInteractor = keyguardKeyboardInteractor;
         mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
@@ -151,10 +147,6 @@
                     verifyPasswordAndUnlock();
                 }
             });
-
-            if (!Flags.simPinTalkbackFixForDoubleSubmit()) {
-                okButton.setOnHoverListener(mLiftToActivateListener);
-            }
         }
         if (pinInputFieldStyledFocusState()) {
             collectFlow(mPasswordEntry, mKeyguardKeyboardInteractor.isAnyKeyboardConnected(),
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index b159a70..9ae4cc6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -56,7 +56,7 @@
             SecurityMode securityMode, LockPatternUtils lockPatternUtils,
             KeyguardSecurityCallback keyguardSecurityCallback,
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
-            LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
+            LatencyTracker latencyTracker,
             EmergencyButtonController emergencyButtonController,
             FalsingCollector falsingCollector,
             DevicePostureController postureController, FeatureFlags featureFlags,
@@ -65,7 +65,7 @@
             BouncerHapticPlayer bouncerHapticPlayer,
             UserActivityNotifier userActivityNotifier) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
-                messageAreaControllerFactory, latencyTracker, liftToActivateListener,
+                messageAreaControllerFactory, latencyTracker,
                 emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor,
                 keyguardKeyboardInteractor, bouncerHapticPlayer, userActivityNotifier);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index 52c93f7..24f77d7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -91,7 +91,7 @@
             SecurityMode securityMode, LockPatternUtils lockPatternUtils,
             KeyguardSecurityCallback keyguardSecurityCallback,
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
-            LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
+            LatencyTracker latencyTracker,
             TelephonyManager telephonyManager, FalsingCollector falsingCollector,
             EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags,
             SelectedUserInteractor selectedUserInteractor,
@@ -99,7 +99,7 @@
             BouncerHapticPlayer bouncerHapticPlayer,
             UserActivityNotifier userActivityNotifier) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
-                messageAreaControllerFactory, latencyTracker, liftToActivateListener,
+                messageAreaControllerFactory, latencyTracker,
                 emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor,
                 keyguardKeyboardInteractor, bouncerHapticPlayer, userActivityNotifier);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
index 9adc5ba..e17e8cc 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -89,7 +89,7 @@
             SecurityMode securityMode, LockPatternUtils lockPatternUtils,
             KeyguardSecurityCallback keyguardSecurityCallback,
             KeyguardMessageAreaController.Factory messageAreaControllerFactory,
-            LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
+            LatencyTracker latencyTracker,
             TelephonyManager telephonyManager, FalsingCollector falsingCollector,
             EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags,
             SelectedUserInteractor selectedUserInteractor,
@@ -97,7 +97,7 @@
             BouncerHapticPlayer bouncerHapticPlayer,
             UserActivityNotifier userActivityNotifier) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
-                messageAreaControllerFactory, latencyTracker, liftToActivateListener,
+                messageAreaControllerFactory, latencyTracker,
                 emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor,
                 keyguardKeyboardInteractor, bouncerHapticPlayer, userActivityNotifier);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
diff --git a/packages/SystemUI/src/com/android/keyguard/LiftToActivateListener.java b/packages/SystemUI/src/com/android/keyguard/LiftToActivateListener.java
deleted file mode 100644
index 425e50e..0000000
--- a/packages/SystemUI/src/com/android/keyguard/LiftToActivateListener.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2013 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.keyguard;
-
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.accessibility.AccessibilityManager;
-
-import javax.inject.Inject;
-
-/**
- * Hover listener that implements lift-to-activate interaction for
- * accessibility. May be added to multiple views.
- */
-class LiftToActivateListener implements View.OnHoverListener {
-    /** Manager used to query accessibility enabled state. */
-    private final AccessibilityManager mAccessibilityManager;
-
-    private boolean mCachedClickableState;
-
-    @Inject
-    LiftToActivateListener(AccessibilityManager accessibilityManager) {
-        mAccessibilityManager = accessibilityManager;
-    }
-
-    @Override
-    public boolean onHover(View v, MotionEvent event) {
-        // When touch exploration is turned on, lifting a finger while
-        // inside the view bounds should perform a click action.
-        if (mAccessibilityManager.isEnabled()
-                && mAccessibilityManager.isTouchExplorationEnabled()) {
-            switch (event.getActionMasked()) {
-                case MotionEvent.ACTION_HOVER_ENTER:
-                    // Lift-to-type temporarily disables double-tap
-                    // activation by setting the view as not clickable.
-                    mCachedClickableState = v.isClickable();
-                    v.setClickable(false);
-                    break;
-                case MotionEvent.ACTION_HOVER_EXIT:
-                    final int x = (int) event.getX();
-                    final int y = (int) event.getY();
-                    if ((x > v.getPaddingLeft()) && (y > v.getPaddingTop())
-                            && (x < v.getWidth() - v.getPaddingRight())
-                            && (y < v.getHeight() - v.getPaddingBottom())) {
-                        v.performClick();
-                    }
-                    v.setClickable(mCachedClickableState);
-                    break;
-            }
-        }
-
-        // Pass the event to View.onHoverEvent() to handle accessibility.
-        v.onHoverEvent(event);
-
-        // Consume the event so it doesn't fall through to other views.
-        return true;
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/KairosActivatable.kt b/packages/SystemUI/src/com/android/systemui/KairosActivatable.kt
new file mode 100644
index 0000000..5e29ba9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/KairosActivatable.kt
@@ -0,0 +1,212 @@
+/*
+ * 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
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.kairos.BuildScope
+import com.android.systemui.kairos.Events
+import com.android.systemui.kairos.EventsLoop
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.Incremental
+import com.android.systemui.kairos.IncrementalLoop
+import com.android.systemui.kairos.KairosNetwork
+import com.android.systemui.kairos.State
+import com.android.systemui.kairos.StateLoop
+import com.android.systemui.kairos.launchKairosNetwork
+import com.android.systemui.kairos.launchScope
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import dagger.multibindings.Multibinds
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * A Kairos-powered class that needs late-initialization within a Kairos [BuildScope].
+ *
+ * If your class is a [SysUISingleton], you can leverage Dagger to automatically initialize your
+ * instance after SystemUI has initialized:
+ * ```kotlin
+ * class MyClass : KairosActivatable { ... }
+ *
+ * @dagger.Module
+ * interface MyModule {
+ *     @Binds
+ *     @IntoSet
+ *     fun bindKairosActivatable(impl: MyClass): KairosActivatable
+ * }
+ * ```
+ *
+ * Alternatively, you can utilize Dagger's [dagger.assisted.AssistedInject]:
+ * ```kotlin
+ * class MyClass @AssistedInject constructor(...) : KairosActivatable {
+ *     @AssistedFactory
+ *     interface Factory {
+ *         fun create(...): MyClass
+ *     }
+ * }
+ *
+ * // When you need an instance:
+ *
+ * class OtherClass @Inject constructor(
+ *     private val myClassFactory: MyClass.Factory,
+ * ) {
+ *     fun BuildScope.foo() {
+ *         val myClass = activated { myClassFactory.create() }
+ *         ...
+ *     }
+ * }
+ * ```
+ *
+ * @see activated
+ */
+@ExperimentalKairosApi
+fun interface KairosActivatable {
+    /** Initializes any Kairos fields that require a [BuildScope] in order to be constructed. */
+    fun BuildScope.activate()
+}
+
+/** Constructs [KairosActivatable] instances. */
+@ExperimentalKairosApi
+fun interface KairosActivatableFactory<T : KairosActivatable> {
+    fun BuildScope.create(): T
+}
+
+/** Instantiates, [activates][KairosActivatable.activate], and returns a [KairosActivatable]. */
+@ExperimentalKairosApi
+fun <T : KairosActivatable> BuildScope.activated(factory: KairosActivatableFactory<T>): T =
+    factory.run { create() }.apply { activate() }
+
+/**
+ * Utilities for defining [State] and [Events] from a constructor without a provided [BuildScope].
+ * These instances are not active until the builder is [activated][activate]; while you can
+ * immediately use them with other Kairos APIs, the Kairos transaction will be suspended until
+ * initialization is complete.
+ *
+ * ```kotlin
+ * class MyRepository(private val dataSource: DataSource) : KairosBuilder by kairosBuilder() {
+ *   val dataSourceEvent = buildEvents<SomeData> {
+ *       // inside this lambda, we have access to a BuildScope, which can be used to create
+ *       // new inputs to the Kairos network
+ *       dataSource.someDataFlow.toEvents()
+ *   }
+ * }
+ * ```
+ */
+@ExperimentalKairosApi
+interface KairosBuilder : KairosActivatable {
+    /**
+     * Returns a forward-reference to a [State] that will be instantiated when this builder is
+     * [activated][activate].
+     */
+    fun <R> buildState(block: BuildScope.() -> State<R>): State<R>
+
+    /**
+     * Returns a forward-reference to an [Events] that will be instantiated when this builder is
+     * [activated][activate].
+     */
+    fun <R> buildEvents(block: BuildScope.() -> Events<R>): Events<R>
+
+    fun <K, V> buildIncremental(block: BuildScope.() -> Incremental<K, V>): Incremental<K, V>
+
+    /** Defers [block] until this builder is [activated][activate]. */
+    fun onActivated(block: BuildScope.() -> Unit)
+}
+
+/** Returns an [KairosBuilder] that can only be [activated][KairosActivatable.activate] once. */
+@ExperimentalKairosApi fun kairosBuilder(): KairosBuilder = KairosBuilderImpl()
+
+@OptIn(ExperimentalKairosApi::class)
+private class KairosBuilderImpl @Inject constructor() : KairosBuilder {
+
+    // TODO: atomic?
+    // TODO: are two lists really necessary?
+    private var _builds: MutableList<KairosActivatable>? = mutableListOf()
+    private var _startables: MutableList<KairosActivatable>? = mutableListOf()
+
+    private val startables
+        get() = checkNotNull(_startables) { "Kairos network has already been initialized" }
+
+    private val builds
+        get() = checkNotNull(_builds) { "Kairos network has already been initialized" }
+
+    override fun <R> buildState(block: BuildScope.() -> State<R>): State<R> =
+        StateLoop<R>().apply { builds.add { loopback = block() } }
+
+    override fun <R> buildEvents(block: BuildScope.() -> Events<R>): Events<R> =
+        EventsLoop<R>().apply { builds.add { loopback = block() } }
+
+    override fun <K, V> buildIncremental(
+        block: BuildScope.() -> Incremental<K, V>
+    ): Incremental<K, V> = IncrementalLoop<K, V>().apply { builds.add { loopback = block() } }
+
+    override fun onActivated(block: BuildScope.() -> Unit) {
+        startables.add { block() }
+    }
+
+    override fun BuildScope.activate() {
+        builds.forEach { it.run { activate() } }
+        _builds = null
+        deferredBuildScopeAction {
+            startables.forEach { it.run { activate() } }
+            _startables = null
+        }
+    }
+}
+
+/** Initializes [KairosActivatables][KairosActivatable] after SystemUI is initialized. */
+@SysUISingleton
+@ExperimentalKairosApi
+class KairosCoreStartable
+@Inject
+constructor(
+    @Application private val appScope: CoroutineScope,
+    private val kairosNetwork: KairosNetwork,
+    private val activatables: dagger.Lazy<Set<@JvmSuppressWildcards KairosActivatable>>,
+) : CoreStartable {
+    override fun start() {
+        appScope.launch {
+            kairosNetwork.activateSpec {
+                for (activatable in activatables.get()) {
+                    launchScope { activatable.run { activate() } }
+                }
+            }
+        }
+    }
+}
+
+@Module
+@ExperimentalKairosApi
+interface KairosCoreStartableModule {
+    @Binds
+    @IntoMap
+    @ClassKey(KairosCoreStartable::class)
+    fun bindCoreStartable(impl: KairosCoreStartable): CoreStartable
+
+    @Multibinds fun kairosActivatables(): Set<@JvmSuppressWildcards KairosActivatable>
+
+    companion object {
+        @Provides
+        @SysUISingleton
+        fun provideKairosNetwork(@Application scope: CoroutineScope): KairosNetwork =
+            scope.launchKairosNetwork()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
index 5b43346..5cba464 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
@@ -609,15 +609,12 @@
         final WindowMagnificationController windowMagnificationController =
                 mWindowMagnificationControllerSupplier.get(displayId);
         if (windowMagnificationController != null) {
-            boolean isWindowMagnifierActivated = windowMagnificationController.isActivated();
-            if (isWindowMagnifierActivated) {
-                windowMagnificationController.updateDragHandleResourcesIfNeeded(shown);
-            }
+            windowMagnificationController.updateDragHandleResourcesIfNeeded(shown);
 
             if (shown) {
                 mA11yLogger.logWithPosition(
                         MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_OPENED,
-                        isWindowMagnifierActivated
+                        windowMagnificationController.isActivated()
                                 ? ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW
                                 : ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN
                 );
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 08d3e17..1587ab1 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -1519,12 +1519,12 @@
     }
 
     void updateDragHandleResourcesIfNeeded(boolean settingsPanelIsShown) {
+        mSettingsPanelVisibility = settingsPanelIsShown;
+
         if (!isActivated()) {
             return;
         }
 
-        mSettingsPanelVisibility = settingsPanelIsShown;
-
         mDragView.setBackground(mContext.getResources().getDrawable(settingsPanelIsShown
                 ? R.drawable.accessibility_window_magnification_drag_handle_background_change_inset
                 : R.drawable.accessibility_window_magnification_drag_handle_background_inset));
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
index 030d147..edbede8 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
@@ -24,6 +24,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.util.Log;
+import android.view.DisplayCutout;
 import android.view.View;
 import android.view.animation.Animation;
 import android.view.animation.OvershootInterpolator;
@@ -197,7 +198,7 @@
         constrainPositionAndUpdate(position, /* writeToPosition = */ true);
     }
 
-    void flingMenuThenSpringToEdge(float x, float velocityX, float velocityY) {
+    void flingMenuThenSpringToEdge(PointF position, float velocityX, float velocityY) {
         final boolean shouldMenuFlingLeft = isOnLeftSide()
                 ? velocityX < ESCAPE_VELOCITY
                 : velocityX < -ESCAPE_VELOCITY;
@@ -205,9 +206,17 @@
         final Rect draggableBounds = mMenuView.getMenuDraggableBounds();
         final float finalPositionX = shouldMenuFlingLeft
                 ? draggableBounds.left : draggableBounds.right;
-
+        final DisplayCutout displayCutout = mMenuViewAppearance.getDisplayCutout();
+        final float finalPositionY =
+                (displayCutout == null) ? position.y
+                        : mMenuViewAppearance.avoidVerticalDisplayCutout(
+                                position.y, draggableBounds,
+                                shouldMenuFlingLeft
+                                        ? displayCutout.getBoundingRectLeft()
+                                        : displayCutout.getBoundingRectRight()
+                        );
         final float minimumVelocityToReachEdge =
-                (finalPositionX - x) * (FLING_FRICTION_SCALAR * DEFAULT_FRICTION);
+                (finalPositionX - position.x) * (FLING_FRICTION_SCALAR * DEFAULT_FRICTION);
 
         final float startXVelocity = shouldMenuFlingLeft
                 ? Math.min(minimumVelocityToReachEdge, velocityX)
@@ -219,11 +228,19 @@
                 createSpringForce(),
                 finalPositionX);
 
-        flingThenSpringMenuWith(DynamicAnimation.TRANSLATION_Y,
-                velocityY,
-                FLING_FRICTION_SCALAR,
-                createSpringForce(),
-                /* finalPosition= */ null);
+        if (com.android.systemui.Flags.floatingMenuDisplayCutoutSupport()) {
+            flingThenSpringMenuWith(DynamicAnimation.TRANSLATION_Y,
+                    velocityY,
+                    FLING_FRICTION_SCALAR,
+                    createSpringForce(),
+                    (finalPositionY != position.y) ? finalPositionY : null);
+        } else {
+            flingThenSpringMenuWith(DynamicAnimation.TRANSLATION_Y,
+                    velocityY,
+                    FLING_FRICTION_SCALAR,
+                    createSpringForce(),
+                    /* finalPosition= */ null);
+        }
     }
 
     private void flingThenSpringMenuWith(DynamicAnimation.ViewProperty property, float velocity,
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
index a1cb036..bb6ab51 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
@@ -266,7 +266,7 @@
                 mSecureSettings.getUriFor(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS),
                 /* notifyForDescendants */ false, mMenuTargetFeaturesContentObserver,
                 UserHandle.USER_CURRENT);
-        if (!com.android.systemui.Flags.floatingMenuNarrowTargetContentObserver()) {
+        if (com.android.systemui.Flags.floatingMenuNotifyTargetsChangedOnStrictDiff()) {
             mSecureSettings.registerContentObserverForUserSync(
                     mSecureSettings.getUriFor(ENABLED_ACCESSIBILITY_SERVICES),
                     /* notifyForDescendants */ false,
@@ -287,7 +287,7 @@
                 UserHandle.USER_CURRENT);
         mContext.registerComponentCallbacks(mComponentCallbacks);
 
-        if (!com.android.systemui.Flags.floatingMenuNarrowTargetContentObserver()) {
+        if (com.android.systemui.Flags.floatingMenuNotifyTargetsChangedOnStrictDiff()) {
             mAccessibilityManager.addAccessibilityServicesStateChangeListener(
                     mA11yServicesStateChangeListener);
         }
@@ -317,7 +317,7 @@
         mContext.getContentResolver().unregisterContentObserver(mMenuFadeOutContentObserver);
         mContext.unregisterComponentCallbacks(mComponentCallbacks);
 
-        if (!com.android.systemui.Flags.floatingMenuNarrowTargetContentObserver()) {
+        if (com.android.systemui.Flags.floatingMenuNotifyTargetsChangedOnStrictDiff()) {
             mAccessibilityManager.removeAccessibilityServicesStateChangeListener(
                     mA11yServicesStateChangeListener);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
index 9511e37..aca020d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
@@ -105,7 +105,8 @@
                     if (mDragToInteractAnimationController.maybeConsumeUpMotionEvent(motionEvent)
                             == empty) {
                         mVelocityTracker.computeCurrentVelocity(VELOCITY_UNIT_SECONDS);
-                        mMenuAnimationController.flingMenuThenSpringToEdge(endX,
+                        mMenuAnimationController.flingMenuThenSpringToEdge(
+                                new PointF(endX, mMenuTranslationDown.y + dy),
                                 mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
                         mMenuAnimationController.fadeOutIfEnabled();
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
index 3f49010..ae39b72 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
@@ -284,13 +284,36 @@
         onEdgeChanged();
         onPositionChanged();
 
-        if (mFeaturesChangeListener != null) {
+        boolean shouldSendFeatureChangeNotification =
+                com.android.systemui.Flags.floatingMenuNotifyTargetsChangedOnStrictDiff()
+                    ? !areFeatureListsIdentical(targetFeatures, newTargetFeatures)
+                    : true;
+        if (mFeaturesChangeListener != null && shouldSendFeatureChangeNotification) {
             mFeaturesChangeListener.onChange(newTargetFeatures);
         }
 
         mMenuAnimationController.fadeOutIfEnabled();
     }
 
+    /**
+     * Returns true if the given feature lists are identical lists, i.e. the same list of {@link
+     * AccessibilityTarget} (equality checked via UID) in the same order.
+     */
+    private boolean areFeatureListsIdentical(
+            List<AccessibilityTarget> currentFeatures, List<AccessibilityTarget> newFeatures) {
+        if (currentFeatures.size() != newFeatures.size()) {
+            return false;
+        }
+
+        for (int i = 0; i < currentFeatures.size(); i++) {
+            if (currentFeatures.get(i).getUid() != newFeatures.get(i).getUid()) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
     private void onMenuFadeEffectInfoChanged(MenuFadeEffectInfo fadeEffectInfo) {
         mMenuAnimationController.updateOpacityWith(fadeEffectInfo.isFadeEffectEnabled(),
                 fadeEffectInfo.getOpacity());
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
index a700cbe..bd3dfe0 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
@@ -28,12 +28,14 @@
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.view.DisplayCutout;
 import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.WindowMetrics;
 
 import androidx.annotation.DimenRes;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.res.R;
 
 import java.lang.annotation.Retention;
@@ -291,7 +293,7 @@
         final WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics();
         final WindowInsets windowInsets = windowMetrics.getWindowInsets();
         final Insets insets = windowInsets.getInsetsIgnoringVisibility(
-                WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout());
+                WindowInsets.Type.systemBars());
 
         final Rect bounds = new Rect(windowMetrics.getBounds());
         bounds.left += insets.left;
@@ -302,6 +304,37 @@
         return bounds;
     }
 
+    DisplayCutout getDisplayCutout() {
+        return mWindowManager.getCurrentWindowMetrics().getWindowInsets().getDisplayCutout();
+    }
+
+    float avoidVerticalDisplayCutout(float y, Rect bounds, Rect cutout) {
+        int menuHeight = calculateActualMenuHeight();
+        return avoidVerticalDisplayCutout(y, menuHeight, bounds, cutout);
+    }
+
+    @VisibleForTesting
+    public static float avoidVerticalDisplayCutout(
+            float y, float menuHeight, Rect bounds, Rect cutout) {
+        if (cutout.top > y + menuHeight || cutout.bottom < y) {
+            return y;
+        }
+
+        boolean topAvailable = cutout.top - bounds.top >= menuHeight;
+        boolean bottomAvailable = bounds.bottom - cutout.bottom >= menuHeight;
+        boolean topOrBottom;
+        if (!topAvailable && !bottomAvailable) {
+            return y;
+        } else if (topAvailable && !bottomAvailable) {
+            topOrBottom = true;
+        } else if (!topAvailable && bottomAvailable) {
+            topOrBottom = false;
+        } else {
+            topOrBottom = y + menuHeight * 0.5f < cutout.centerY();
+        }
+        return (topOrBottom) ? cutout.top - menuHeight : cutout.bottom;
+    }
+
     boolean isMenuOnLeftSide() {
         return mPercentagePosition.getPercentageX() < 0.5f;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index 7a674e2..8109522 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -21,17 +21,12 @@
 import static androidx.core.view.WindowInsetsCompat.Type;
 
 import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_BUTTON_COMPONENT_NAME;
-import static com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType.INVISIBLE_TOGGLE;
-import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
-import static com.android.internal.accessibility.util.AccessibilityUtils.getAccessibilityServiceFragmentType;
-import static com.android.internal.accessibility.util.AccessibilityUtils.setAccessibilityServiceState;
 import static com.android.systemui.accessibility.floatingmenu.MenuMessageView.Index;
 import static com.android.systemui.accessibility.floatingmenu.MenuNotificationFactory.ACTION_DELETE;
 import static com.android.systemui.accessibility.floatingmenu.MenuNotificationFactory.ACTION_UNDO;
 import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat;
 
-import android.accessibilityservice.AccessibilityServiceInfo;
 import android.annotation.IntDef;
 import android.annotation.StringDef;
 import android.annotation.SuppressLint;
@@ -39,7 +34,6 @@
 import android.app.StatusBarManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentCallbacks;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -174,46 +168,13 @@
     final Runnable mDismissMenuAction = new Runnable() {
         @Override
         public void run() {
-            if (android.view.accessibility.Flags.a11yQsShortcut()) {
-                mAccessibilityManager.enableShortcutsForTargets(
-                        /* enable= */ false,
-                        ShortcutConstants.UserShortcutType.SOFTWARE,
-                        new ArraySet<>(
-                                mAccessibilityManager.getAccessibilityShortcutTargets(SOFTWARE)),
-                        mSecureSettings.getRealUserHandle(UserHandle.USER_CURRENT)
-                );
-            } else {
-                mSecureSettings.putStringForUser(
-                        Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, /* value= */ "",
-                        UserHandle.USER_CURRENT);
-
-                final List<ComponentName> hardwareKeyShortcutComponents =
-                        mAccessibilityManager.getAccessibilityShortcutTargets(HARDWARE)
-                                .stream()
-                                .map(ComponentName::unflattenFromString)
-                                .toList();
-
-                // Should disable the corresponding service when the fragment type is
-                // INVISIBLE_TOGGLE, which will enable service when the shortcut is on.
-                final List<AccessibilityServiceInfo> serviceInfoList =
-                        mAccessibilityManager.getEnabledAccessibilityServiceList(
-                                AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
-                serviceInfoList.forEach(info -> {
-                    if (getAccessibilityServiceFragmentType(info) != INVISIBLE_TOGGLE) {
-                        return;
-                    }
-
-                    final ComponentName serviceComponentName = info.getComponentName();
-                    if (hardwareKeyShortcutComponents.contains(serviceComponentName)) {
-                        return;
-                    }
-
-                    setAccessibilityServiceState(
-                            getContext(), serviceComponentName, /* enabled= */ false,
-                            mSecureSettings.getRealUserHandle(UserHandle.USER_CURRENT));
-                });
-            }
-
+            mAccessibilityManager.enableShortcutsForTargets(
+                    /* enable= */ false,
+                    ShortcutConstants.UserShortcutType.SOFTWARE,
+                    new ArraySet<>(
+                            mAccessibilityManager.getAccessibilityShortcutTargets(SOFTWARE)),
+                    mSecureSettings.getRealUserHandle(UserHandle.USER_CURRENT)
+            );
             mFloatingMenu.hide();
         }
     };
@@ -516,7 +477,7 @@
             return;
         }
         mMenuAnimationController.flingMenuThenSpringToEdge(
-                mMenuView.getMenuPosition().x, 100f, 0f);
+                mMenuView.getMenuPosition(), 100f, 0f);
 
         Intent intent = getIntentForEditScreen();
         PackageManager packageManager = getContext().getPackageManager();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
index 73aabc3..438184d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -286,9 +286,7 @@
         if (com.android.settingslib.flags.Flags.hearingDevicesAmbientVolumeControl()) {
             setupAmbientControls();
         }
-        if (com.android.systemui.Flags.hearingDevicesDialogRelatedTools()) {
-            setupRelatedToolsView(dialog);
-        }
+        setupRelatedToolsView(dialog);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogReceiver.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogReceiver.java
index 02e65fd..90b8fa0 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogReceiver.java
@@ -22,8 +22,6 @@
 import android.content.Context;
 import android.content.Intent;
 
-import com.android.systemui.Flags;
-
 import javax.inject.Inject;
 
 /**
@@ -43,10 +41,6 @@
 
     @Override
     public void onReceive(Context context, Intent intent) {
-        if (!Flags.hearingAidsQsTileDialog()) {
-            return;
-        }
-
         if (ACTION.equals(intent.getAction())) {
             mDialogManager.showDialog(/* expandable= */ null, LAUNCH_SOURCE_A11Y);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
index 610e3f8a..fb47d42 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
@@ -411,7 +411,7 @@
             stateInteractor: HearingDevicesTileDataInteractor,
             userActionInteractor: HearingDevicesTileUserActionInteractor,
         ): QSTileViewModel {
-            return if (Flags.hearingAidsQsTileDialog() && Flags.qsNewTilesFuture()) {
+            return if (Flags.qsNewTilesFuture()) {
                 factory.create(
                     TileSpec.create(HEARING_DEVICES_TILE_SPEC),
                     userActionInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
index 68ec0f2d..39f5580 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
@@ -68,7 +68,7 @@
     val sensorType: StateFlow<FingerprintSensorType>
 
     /** The sensor location relative to each physical display. */
-    val sensorLocations: Flow<Map<String, SensorLocationInternal>>
+    val sensorLocations: StateFlow<Map<String, SensorLocationInternal>>
 }
 
 @SysUISingleton
@@ -128,12 +128,14 @@
                 initialValue = props.value.sensorType.toSensorType(),
             )
 
-    override val sensorLocations: Flow<Map<String, SensorLocationInternal>> =
-        props.map {
-            it.allLocations.associateBy { sensorLocationInternal ->
-                sensorLocationInternal.displayId
-            }
-        }
+    override val sensorLocations: StateFlow<Map<String, SensorLocationInternal>> =
+        props
+            .map { props -> props.allLocations.associateBy { it.displayId } }
+            .stateIn(
+                applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = props.value.allLocations.associateBy { it.displayId },
+            )
 
     override val propertiesInitialized: Flow<Boolean> =
         combine(
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
index d9ed9ca..ae855d1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
@@ -20,11 +20,11 @@
 import android.graphics.Rect
 import android.hardware.biometrics.SensorLocationInternal
 import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
-import com.android.systemui.biometrics.shared.model.SensorLocation
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.shared.customization.data.SensorLocation
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
@@ -42,8 +42,8 @@
 constructor(
     @Application private val applicationScope: CoroutineScope,
     @Application private val context: Context,
-    repository: FingerprintPropertyRepository,
-    @Main configurationInteractor: ConfigurationInteractor,
+    private val repository: FingerprintPropertyRepository,
+    @Main private val configurationInteractor: ConfigurationInteractor,
     displayStateInteractor: DisplayStateInteractor,
     udfpsOverlayInteractor: UdfpsOverlayInteractor,
 ) {
@@ -61,11 +61,16 @@
      * Devices with multiple physical displays use unique display ids to determine which sensor is
      * on the active physical display. This value represents a unique physical display id.
      */
-    private val uniqueDisplayId: Flow<String> =
+    private val uniqueDisplayId: StateFlow<String> =
         displayStateInteractor.displayChanges
-            .map { context.display?.uniqueId }
+            .map { context.display.uniqueId }
             .filterNotNull()
             .distinctUntilChanged()
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = EMPTY_DISPLAY_ID,
+            )
 
     /**
      * Sensor location for the:
@@ -73,13 +78,15 @@
      * - device's natural screen resolution
      * - device's natural orientation
      */
-    private val unscaledSensorLocation: Flow<SensorLocationInternal> =
-        combine(repository.sensorLocations, uniqueDisplayId) { locations, displayId ->
+    private val unscaledSensorLocation: StateFlow<SensorLocationInternal> =
+        combineStates(repository.sensorLocations, uniqueDisplayId, applicationScope) {
+            locations,
+            displayId ->
             // Devices without multiple physical displays do not use the display id as the key;
             // instead, the key is an empty string.
             locations.getOrDefault(
                 displayId,
-                locations.getOrDefault("", SensorLocationInternal.DEFAULT),
+                locations.getOrDefault(EMPTY_DISPLAY_ID, SensorLocationInternal.DEFAULT),
             )
         }
 
@@ -89,18 +96,18 @@
      * - current screen resolution
      * - device's natural orientation
      */
-    val sensorLocation: Flow<SensorLocation> =
-        combine(unscaledSensorLocation, configurationInteractor.scaleForResolution) {
+    val sensorLocation: StateFlow<SensorLocation> =
+        combineStates(
             unscaledSensorLocation,
-            scale ->
-            val sensorLocation =
-                SensorLocation(
-                    naturalCenterX = unscaledSensorLocation.sensorLocationX,
-                    naturalCenterY = unscaledSensorLocation.sensorLocationY,
-                    naturalRadius = unscaledSensorLocation.sensorRadius,
-                    scale = scale,
-                )
-            sensorLocation
+            configurationInteractor.scaleForResolution,
+            applicationScope,
+        ) { unscaledSensorLocation, scale ->
+            SensorLocation(
+                naturalCenterX = unscaledSensorLocation.sensorLocationX,
+                naturalCenterY = unscaledSensorLocation.sensorLocationY,
+                naturalRadius = unscaledSensorLocation.sensorRadius,
+                scale = scale,
+            )
         }
 
     /**
@@ -111,4 +118,19 @@
      */
     val udfpsSensorBounds: Flow<Rect> =
         udfpsOverlayInteractor.udfpsOverlayParams.map { it.sensorBounds }.distinctUntilChanged()
+
+    companion object {
+
+        private const val EMPTY_DISPLAY_ID = ""
+
+        /** Combine two state flows to another state flow. */
+        private fun <T1, T2, R> combineStates(
+            flow1: StateFlow<T1>,
+            flow2: StateFlow<T2>,
+            scope: CoroutineScope,
+            transform: (T1, T2) -> R,
+        ): StateFlow<R> =
+            combine(flow1, flow2) { v1, v2 -> transform(v1, v2) }
+                .stateIn(scope, SharingStarted.Eagerly, transform(flow1.value, flow2.value))
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt
index 22b2888..a447786 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.bouncer.data.repository
 
+import android.annotation.SuppressLint
 import android.os.Build
 import android.util.Log
 import com.android.keyguard.KeyguardSecurityModel
@@ -51,7 +52,11 @@
     val primaryBouncerShow: StateFlow<Boolean>
     val primaryBouncerShowingSoon: StateFlow<Boolean>
     val primaryBouncerStartingToHide: StateFlow<Boolean>
-    val primaryBouncerStartingDisappearAnimation: StateFlow<Runnable?>
+    val primaryBouncerStartingDisappearAnimation: MutableSharedFlow<Runnable?>
+
+    fun isPrimaryBouncerStartingDisappearAnimation(): Boolean
+
+    fun isDebuggable(): Boolean
 
     /** Determines if we want to instantaneously show the primary bouncer instead of translating. */
     val primaryBouncerScrimmed: StateFlow<Boolean>
@@ -128,7 +133,7 @@
 }
 
 @SysUISingleton
-class KeyguardBouncerRepositoryImpl
+open class KeyguardBouncerRepositoryImpl
 @Inject
 constructor(
     private val clock: SystemClock,
@@ -144,9 +149,19 @@
     override val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
     private val _primaryBouncerStartingToHide = MutableStateFlow(false)
     override val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
-    private val _primaryBouncerDisappearAnimation = MutableStateFlow<Runnable?>(null)
+
+    @SuppressLint("SharedFlowCreation")
     override val primaryBouncerStartingDisappearAnimation =
-        _primaryBouncerDisappearAnimation.asStateFlow()
+        MutableSharedFlow<Runnable?>(extraBufferCapacity = 2, replay = 1)
+
+    override fun isPrimaryBouncerStartingDisappearAnimation(): Boolean {
+        val replayCache = primaryBouncerStartingDisappearAnimation.replayCache
+        return if (!replayCache.isEmpty()) {
+            replayCache.last() != null
+        } else {
+            false
+        }
+    }
 
     /** Determines if we want to instantaneously show the primary bouncer instead of translating. */
     private val _primaryBouncerScrimmed = MutableStateFlow(false)
@@ -177,6 +192,7 @@
         _keyguardAuthenticatedPrimaryAuth.asSharedFlow()
 
     /** Whether the user requested to show the bouncer when device is already authenticated */
+    @SuppressLint("SharedFlowCreation")
     private val _userRequestedBouncerWhenAlreadyAuthenticated = MutableSharedFlow<Int>()
     override val userRequestedBouncerWhenAlreadyAuthenticated: Flow<Int> =
         _userRequestedBouncerWhenAlreadyAuthenticated.asSharedFlow()
@@ -226,7 +242,7 @@
     }
 
     override fun setPrimaryStartDisappearAnimation(runnable: Runnable?) {
-        _primaryBouncerDisappearAnimation.value = runnable
+        primaryBouncerStartingDisappearAnimation.tryEmit(runnable)
     }
 
     override fun setPanelExpansion(panelExpansion: Float) {
@@ -265,9 +281,11 @@
         _lastShownSecurityMode.value = securityMode
     }
 
+    override fun isDebuggable() = Build.IS_DEBUGGABLE
+
     /** Sets up logs for state flows. */
     private fun setUpLogging() {
-        if (!Build.IS_DEBUGGABLE) {
+        if (!isDebuggable()) {
             return
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
index 641400a..0c6d792 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
@@ -22,6 +22,7 @@
 import android.os.Trace
 import android.util.Log
 import android.view.View
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.DejankUtils
@@ -54,7 +55,6 @@
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.map
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /**
  * Encapsulates business logic for interacting with the lock-screen primary (pin/pattern/password)
@@ -145,7 +145,7 @@
                 TAG,
                 "PrimaryBouncerInteractor#show is being called before the " +
                     "primaryBouncerDelegate is set. Let's exit early so we don't " +
-                    "set the wrong primaryBouncer state."
+                    "set the wrong primaryBouncer state.",
             )
             return false
         }
@@ -197,7 +197,7 @@
         if (isFullyShowing()) {
             SysUiStatsLog.write(
                 SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED,
-                SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__HIDDEN
+                SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__HIDDEN,
             )
             dismissCallbackRegistry.notifyDismissCancelled()
         }
@@ -223,7 +223,7 @@
     fun setPanelExpansion(expansion: Float) {
         val oldExpansion = repository.panelExpansionAmount.value
         val expansionChanged = oldExpansion != expansion
-        if (repository.primaryBouncerStartingDisappearAnimation.value == null) {
+        if (!repository.isPrimaryBouncerStartingDisappearAnimation()) {
             repository.setPanelExpansion(expansion)
         }
 
@@ -272,7 +272,7 @@
      */
     fun setDismissAction(
         onDismissAction: ActivityStarter.OnDismissAction?,
-        cancelAction: Runnable?
+        cancelAction: Runnable?,
     ) {
         repository.bouncerDismissActionModel =
             if (onDismissAction != null && cancelAction != null) {
@@ -344,7 +344,7 @@
     fun isFullyShowing(): Boolean {
         return (repository.primaryBouncerShowingSoon.value || isBouncerShowing()) &&
             repository.panelExpansionAmount.value == KeyguardBouncerConstants.EXPANSION_VISIBLE &&
-            repository.primaryBouncerStartingDisappearAnimation.value == null
+            !repository.isPrimaryBouncerStartingDisappearAnimation()
     }
 
     /** Returns whether bouncer is scrimmed. */
@@ -361,7 +361,7 @@
 
     /** Return whether bouncer is animating away. */
     fun isAnimatingAway(): Boolean {
-        return repository.primaryBouncerStartingDisappearAnimation.value != null
+        return repository.isPrimaryBouncerStartingDisappearAnimation()
     }
 
     /** Return whether bouncer will dismiss with actions */
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
index d5c815d..434a9ce 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
@@ -170,8 +170,6 @@
 
                     launch {
                         viewModel.startDisappearAnimation.collect {
-                            android.util.Log.i("KeyguardBouncerViewBinder",
-                                    "viewModel.startDisappearAnimation: $it")
                             securityContainerController.startDisappearAnimation(it)
                         }
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index 5baec1e..73aaf7f 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -29,11 +29,11 @@
 import android.view.View
 import androidx.compose.ui.input.key.KeyEvent
 import androidx.compose.ui.input.key.KeyEventType
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.keyguard.PinShapeAdapter
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor
-import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
 import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer
 import com.android.systemui.res.R
 import dagger.assisted.Assisted
@@ -50,7 +50,6 @@
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.receiveAsFlow
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /** Holds UI state and handles user input for the PIN code bouncer UI. */
 class PinBouncerViewModel
@@ -289,11 +288,10 @@
      * feedback on the view.
      */
     fun onDigitButtonDown(view: View?) {
-        if (ComposeBouncerFlags.isOnlyComposeBouncerEnabled()) {
-            // Current PIN bouncer informs FalsingInteractor#avoidGesture() upon every Pin button
-            // touch.
-            super.onDown()
-        }
+        // This ends up calling FalsingInteractor#avoidGesture() each time a PIN button is touched.
+        // It helps make sure that legitimate touch in the PIN bouncer isn't treated as false touch.
+        super.onDown()
+
         if (bouncerHapticPlayer?.isEnabled == true) {
             bouncerHapticPlayer.playNumpadKeyFeedback()
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCoreStartable.kt
index b79538a..60cdccd 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCoreStartable.kt
@@ -21,8 +21,9 @@
 
 /** Initializes classes related to falsing. */
 @SysUISingleton
-class FalsingCoreStartable @Inject constructor(val falsingCollector: FalsingCollector) :
-    CoreStartable {
+class FalsingCoreStartable
+@Inject
+constructor(@FalsingCollectorActual val falsingCollector: FalsingCollector) : CoreStartable {
     override fun start() {
         falsingCollector.init()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardImageLoader.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardImageLoader.kt
index 8814a12..c0ad3b8 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardImageLoader.kt
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardImageLoader.kt
@@ -20,15 +20,17 @@
 import android.net.Uri
 import android.util.Log
 import android.util.Size
-import com.android.systemui.res.R
+import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.Flags.clipboardOverlayMultiuser
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.res.R
+import com.android.systemui.settings.UserTracker
 import java.io.IOException
 import java.util.function.Consumer
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
-import com.android.app.tracing.coroutines.launchTraced as launch
 import kotlinx.coroutines.withContext
 import kotlinx.coroutines.withTimeoutOrNull
 
@@ -36,8 +38,9 @@
 @Inject
 constructor(
     private val context: Context,
+    private val userTracker: UserTracker,
     @Background private val bgDispatcher: CoroutineDispatcher,
-    @Application private val mainScope: CoroutineScope
+    @Application private val mainScope: CoroutineScope,
 ) {
     private val TAG: String = "ClipboardImageLoader"
 
@@ -46,7 +49,15 @@
             withContext(bgDispatcher) {
                 try {
                     val size = context.resources.getDimensionPixelSize(R.dimen.overlay_x_scale)
-                    context.contentResolver.loadThumbnail(uri, Size(size, size * 4), null)
+                    if (clipboardOverlayMultiuser()) {
+                        userTracker.userContentResolver.loadThumbnail(
+                            uri,
+                            Size(size, size * 4),
+                            null,
+                        )
+                    } else {
+                        context.contentResolver.loadThumbnail(uri, Size(size, size * 4), null)
+                    }
                 } catch (e: IOException) {
                     Log.e(TAG, "Thumbnail loading failed!", e)
                     null
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
index 747a2a9..7fcdd95 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
@@ -52,7 +52,7 @@
     /** Called whenever the configuration has changed. */
     val onConfigurationChange: Flow<Unit>
 
-    val scaleForResolution: Flow<Float>
+    val scaleForResolution: StateFlow<Float>
 
     val configurationValues: Flow<Configuration>
 
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
index 97a23e1..4d39b033 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.common.ui.data.repository.ConfigurationRepository
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
@@ -60,7 +61,7 @@
     val configurationValues: Flow<Configuration>
 
     /** Emits the current resolution scaling factor */
-    val scaleForResolution: Flow<Float>
+    val scaleForResolution: StateFlow<Float>
 
     /** Given [resourceId], emit the dimension pixel size on config change */
     fun dimensionPixelSize(resourceId: Int): Flow<Int>
@@ -121,5 +122,5 @@
 
     override val configurationValues: Flow<Configuration> = repository.configurationValues
 
-    override val scaleForResolution: Flow<Float> = repository.scaleForResolution
+    override val scaleForResolution: StateFlow<Float> = repository.scaleForResolution
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 7ebe52f..c02784d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -31,6 +31,7 @@
 import com.android.systemui.BootCompleteCacheImpl;
 import com.android.systemui.CameraProtectionModule;
 import com.android.systemui.CoreStartable;
+import com.android.systemui.KairosCoreStartableModule;
 import com.android.systemui.SystemUISecondaryUserService;
 import com.android.systemui.activity.ActivityManagerModule;
 import com.android.systemui.ambient.dagger.AmbientModule;
@@ -232,6 +233,7 @@
         FlagsModule.class,
         FlagDependenciesModule.class,
         FooterActionsModule.class,
+        KairosCoreStartableModule.class,
         GestureModule.class,
         InputMethodModule.class,
         KeyEventRepositoryModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt
index 6c6d730..911327a 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt
@@ -17,10 +17,10 @@
 package com.android.systemui.deviceentry.domain.interactor
 
 import com.android.systemui.biometrics.domain.interactor.FingerprintPropertyInteractor
-import com.android.systemui.biometrics.shared.model.SensorLocation
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
+import com.android.systemui.shared.customization.data.SensorLocation
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
diff --git a/packages/SystemUI/src/com/android/systemui/education/ContextualEducationMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/education/ContextualEducationMetricsLogger.kt
index 9af259a..d10958a 100644
--- a/packages/SystemUI/src/com/android/systemui/education/ContextualEducationMetricsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/ContextualEducationMetricsLogger.kt
@@ -42,8 +42,8 @@
             }
         SysUiStatsLog.write(
             SysUiStatsLog.CONTEXTUAL_EDUCATION_TRIGGERED,
-            statsGestureType,
             statsEducationType,
+            statsGestureType,
         )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt
index 950a727..0ab5a80 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt
@@ -25,6 +25,7 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusRequester
@@ -45,7 +46,10 @@
 fun ActionKeyTutorialScreen(onDoneButtonClicked: () -> Unit, onBack: () -> Unit) {
     BackHandler(onBack = onBack)
     val screenConfig = buildScreenConfig()
-    var actionState: TutorialActionState by remember { mutableStateOf(NotStarted) }
+    var actionState: TutorialActionState by
+        rememberSaveable(stateSaver = TutorialActionState.stateSaver()) {
+            mutableStateOf(NotStarted)
+        }
     val focusRequester = remember { FocusRequester() }
     Box(
         modifier =
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
index c40adfe..21afa40 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
@@ -33,10 +33,13 @@
 import androidx.compose.foundation.layout.width
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
+import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.Saver
+import androidx.compose.runtime.saveable.mapSaver
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusRequester
 import androidx.compose.ui.focus.focusRequester
@@ -45,11 +48,13 @@
 import androidx.compose.ui.platform.LocalConfiguration
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
+import com.android.compose.windowsizeclass.LocalWindowSizeClass
 import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Error
 import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Finished
 import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgress
 import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgressAfterError
 import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.NotStarted
+import com.android.systemui.keyboard.shortcut.ui.composable.hasCompactWindowSize
 
 sealed interface TutorialActionState {
     data object NotStarted : TutorialActionState
@@ -66,6 +71,31 @@
 
     data class InProgressAfterError(val inProgress: InProgress) :
         TutorialActionState, Progress by inProgress
+
+    companion object {
+        fun stateSaver(): Saver<TutorialActionState, Any> {
+            val classKey = "class"
+            val successAnimationKey = "animation"
+            return mapSaver(
+                save = {
+                    buildMap {
+                        put(classKey, it::class.java.name)
+                        if (it is Finished) put(successAnimationKey, it.successAnimation)
+                    }
+                },
+                restore = { map ->
+                    when (map[classKey] as? String) {
+                        NotStarted::class.java.name,
+                        InProgress::class.java.name -> NotStarted
+                        Error::class.java.name,
+                        InProgressAfterError::class.java.name -> Error
+                        Finished::class.java.name -> Finished(map[successAnimationKey]!! as Int)
+                        else -> NotStarted
+                    }
+                },
+            )
+        }
+    }
 }
 
 interface Progress {
@@ -82,18 +112,25 @@
 ) {
     Column(
         verticalArrangement = Arrangement.Center,
-        modifier =
-            Modifier.fillMaxSize()
-                .background(config.colors.background)
-                .safeDrawingPadding()
-                .padding(start = 48.dp, top = 100.dp, end = 48.dp, bottom = 8.dp),
+        modifier = Modifier.fillMaxSize().background(config.colors.background).safeDrawingPadding(),
     ) {
+        val isCompactWindow = hasCompactWindowSize()
         when (LocalConfiguration.current.orientation) {
             Configuration.ORIENTATION_LANDSCAPE -> {
-                HorizontalDescriptionAndAnimation(actionState, config, Modifier.weight(1f))
+                HorizontalDescriptionAndAnimation(
+                    actionState,
+                    config,
+                    isCompactWindow,
+                    Modifier.weight(1f),
+                )
             }
             else -> {
-                VerticalDescriptionAndAnimation(actionState, config, Modifier.weight(1f))
+                VerticalDescriptionAndAnimation(
+                    actionState,
+                    config,
+                    isCompactWindow,
+                    Modifier.weight(1f),
+                )
             }
         }
         val buttonAlpha by animateFloatAsState(if (actionState is Finished) 1f else 0f)
@@ -109,11 +146,15 @@
 private fun HorizontalDescriptionAndAnimation(
     actionState: TutorialActionState,
     config: TutorialScreenConfig,
+    isCompactWindow: Boolean,
     modifier: Modifier = Modifier,
 ) {
-    Row(modifier = modifier.fillMaxWidth()) {
-        TutorialDescription(actionState, config, modifier = Modifier.weight(1f))
-        Spacer(modifier = Modifier.width(70.dp))
+    Row(
+        modifier =
+            modifier.fillMaxWidth().padding(start = 48.dp, top = 100.dp, end = 48.dp, bottom = 8.dp)
+    ) {
+        TutorialDescription(actionState, config, isCompactWindow, modifier = Modifier.weight(1f))
+        Spacer(modifier = Modifier.width(24.dp))
         TutorialAnimation(actionState, config, modifier = Modifier.weight(1f))
     }
 }
@@ -122,20 +163,25 @@
 private fun VerticalDescriptionAndAnimation(
     actionState: TutorialActionState,
     config: TutorialScreenConfig,
+    isCompactWindow: Boolean,
     modifier: Modifier = Modifier,
 ) {
-    Column(modifier = modifier.fillMaxWidth().padding(horizontal = 40.dp, vertical = 40.dp)) {
-        Spacer(modifier = Modifier.weight(0.1f))
+    val horizontalPadding = if (isCompactWindow) 24.dp else 96.dp
+    // Represents the majority of tablets in portrait - we need extra spacer at the top and bottom
+    val isTablet = LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Expanded
+    Column(
+        modifier =
+            modifier.fillMaxWidth().padding(start = 0.dp, top = 100.dp, end = 0.dp, bottom = 8.dp)
+    ) {
+        if (isTablet) Spacer(modifier = Modifier.weight(0.3f))
         TutorialDescription(
             actionState,
             config,
-            modifier =
-                Modifier.weight(0.2f)
-                    // extra padding to better align with animation which has embedded padding
-                    .padding(horizontal = 15.dp),
+            isCompactWindow,
+            modifier = Modifier.weight(1f).padding(horizontal = horizontalPadding),
         )
-        Spacer(modifier = Modifier.width(70.dp))
-        TutorialAnimation(actionState, config, modifier = Modifier.weight(1f))
+        TutorialAnimation(actionState, config, modifier = Modifier.weight(1.8f).fillMaxWidth())
+        if (isTablet) Spacer(modifier = Modifier.weight(0.3f))
     }
 }
 
@@ -143,6 +189,7 @@
 fun TutorialDescription(
     actionState: TutorialActionState,
     config: TutorialScreenConfig,
+    isCompactWindow: Boolean,
     modifier: Modifier = Modifier,
 ) {
     val focusRequester = remember { FocusRequester() }
@@ -159,7 +206,9 @@
     Column(verticalArrangement = Arrangement.Top, modifier = modifier) {
         Text(
             text = stringResource(id = titleTextId),
-            style = MaterialTheme.typography.displayLarge,
+            style =
+                if (isCompactWindow) MaterialTheme.typography.headlineLarge
+                else MaterialTheme.typography.displayMedium,
             color = config.colors.title,
             modifier = Modifier.focusRequester(focusRequester).focusable(),
         )
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt
index b0816ce..dc0d7b6 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt
@@ -131,10 +131,14 @@
 ) {
     val composition by
         rememberLottieComposition(LottieCompositionSpec.RawRes(finishedState.successAnimation))
+    var animationFinished by rememberSaveable(key = "animationFinished") { mutableStateOf(false) }
     val progress by animateLottieCompositionAsState(composition, iterations = 1)
+    if (progress == 1f) {
+        animationFinished = true
+    }
     LottieAnimation(
         composition = composition,
-        progress = { progress },
+        progress = { if (animationFinished) 1f else progress },
         dynamicProperties = animationProperties,
         modifier = Modifier.fillMaxSize(),
     )
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 ba31d08..bf60c9a5 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
@@ -105,6 +105,7 @@
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.text.SpanStyle
 import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.style.Hyphens
 import androidx.compose.ui.text.withStyle
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
@@ -969,7 +970,7 @@
                 Text(
                     fontSize = 18.sp,
                     color = colors.textColor(selected).value,
-                    style = MaterialTheme.typography.titleSmall,
+                    style = MaterialTheme.typography.titleSmall.copy(hyphens = Hyphens.Auto),
                     text = label,
                 )
             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
index 7a72732..18cabad 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
@@ -33,6 +33,7 @@
 import com.android.app.tracing.coroutines.runBlockingTraced as runBlocking
 import com.android.systemui.SystemUIAppComponentFactoryBase
 import com.android.systemui.SystemUIAppComponentFactoryBase.ContextAvailableCallback
+import com.android.systemui.biometrics.domain.interactor.FingerprintPropertyInteractor
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
 import com.android.systemui.keyguard.ui.preview.KeyguardRemotePreviewManager
@@ -46,6 +47,7 @@
 
     @Inject lateinit var interactor: KeyguardQuickAffordanceInteractor
     @Inject lateinit var shadeModeInteractor: ShadeModeInteractor
+    @Inject lateinit var fingerprintPropertyInteractor: FingerprintPropertyInteractor
     @Inject lateinit var previewManager: KeyguardRemotePreviewManager
     @Inject @Main lateinit var mainDispatcher: CoroutineDispatcher
 
@@ -345,6 +347,14 @@
     }
 
     private fun queryRuntimeValues(): Cursor {
+        // If not UDFPS, the udfpsLocation will be null
+        val udfpsLocation =
+            if (fingerprintPropertyInteractor.isUdfps.value) {
+                fingerprintPropertyInteractor.sensorLocation.value
+            } else {
+                null
+            }
+
         return MatrixCursor(
                 arrayOf(
                     Contract.RuntimeValuesTable.Columns.NAME,
@@ -358,6 +368,9 @@
                         if (shadeModeInteractor.isShadeLayoutWide.value) 1 else 0,
                     )
                 )
+                addRow(
+                    arrayOf(Contract.RuntimeValuesTable.KEY_UDFPS_LOCATION, udfpsLocation?.encode())
+                )
             }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 6473628..5baef91 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -3492,7 +3492,7 @@
     public void showSurfaceBehindKeyguard() {
         mSurfaceBehindRemoteAnimationRequested = true;
 
-        if (ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) {
+        if (ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS && !KeyguardWmStateRefactor.isEnabled()) {
             startKeyguardTransition(false /* keyguardShowing */, false /* aodShowing */);
             return;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index f5e0c81..b1a2ec9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -20,7 +20,6 @@
 import android.animation.IntEvaluator
 import com.android.keyguard.KeyguardViewController
 import com.android.systemui.accessibility.domain.interactor.AccessibilityInteractor
-import com.android.systemui.biometrics.shared.model.SensorLocation
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
@@ -34,6 +33,7 @@
 import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shared.customization.data.SensorLocation
 import com.android.systemui.util.kotlin.sample
 import dagger.Lazy
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index 8fe225a..88fdc83 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -100,7 +100,7 @@
         keyguardClockInteractor.clockShouldBeCentered.stateIn(
             scope = applicationScope,
             started = SharingStarted.WhileSubscribed(),
-            initialValue = false,
+            initialValue = true,
         )
 
     // To translate elements below smartspace in weather clock to avoid overlapping between date
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/animation/ColorSchemeTransition.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/animation/ColorSchemeTransition.kt
index 21407f3..02b4037 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/animation/ColorSchemeTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/animation/ColorSchemeTransition.kt
@@ -27,6 +27,7 @@
 import com.android.internal.R
 import com.android.internal.annotations.VisibleForTesting
 import com.android.settingslib.Utils
+import com.android.systemui.Flags
 import com.android.systemui.media.controls.ui.view.MediaViewHolder
 import com.android.systemui.monet.ColorScheme
 import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect
@@ -51,7 +52,7 @@
 open class AnimatingColorTransition(
     private val defaultColor: Int,
     private val extractColor: (ColorScheme) -> Int,
-    private val applyColor: (Int) -> Unit
+    private val applyColor: (Int) -> Unit,
 ) : AnimatorUpdateListener, ColorTransition {
 
     private val argbEvaluator = ArgbEvaluator()
@@ -105,35 +106,52 @@
     private val mediaViewHolder: MediaViewHolder,
     private val multiRippleController: MultiRippleController,
     private val turbulenceNoiseController: TurbulenceNoiseController,
-    animatingColorTransitionFactory: AnimatingColorTransitionFactory
+    animatingColorTransitionFactory: AnimatingColorTransitionFactory,
 ) {
     constructor(
         context: Context,
         mediaViewHolder: MediaViewHolder,
         multiRippleController: MultiRippleController,
-        turbulenceNoiseController: TurbulenceNoiseController
+        turbulenceNoiseController: TurbulenceNoiseController,
     ) : this(
         context,
         mediaViewHolder,
         multiRippleController,
         turbulenceNoiseController,
-        ::AnimatingColorTransition
+        ::AnimatingColorTransition,
     )
+
     var loadingEffect: LoadingEffect? = null
 
-    val bgColor = context.getColor(com.google.android.material.R.color.material_dynamic_neutral20)
+    val bgColor =
+        if (Flags.mediaControlsUiUpdate()) {
+            context.getColor(R.color.materialColorOnSurface)
+        } else {
+            context.getColor(com.google.android.material.R.color.material_dynamic_neutral20)
+        }
+
+    val textColor = context.getColor(R.color.materialColorInverseOnSurface)
+    val buttonBgColor = context.getColor(R.color.materialColorPrimary)
+    val insideButtonColor = context.getColor(R.color.materialColorOnPrimary)
+
     val surfaceColor =
         animatingColorTransitionFactory(bgColor, ::surfaceFromScheme) { surfaceColor ->
             val colorList = ColorStateList.valueOf(surfaceColor)
-            mediaViewHolder.seamlessIcon.imageTintList = colorList
-            mediaViewHolder.seamlessText.setTextColor(surfaceColor)
             mediaViewHolder.albumView.backgroundTintList = colorList
             mediaViewHolder.gutsViewHolder.setSurfaceColor(surfaceColor)
+
+            if (Flags.mediaControlsUiUpdate()) return@animatingColorTransitionFactory
+            mediaViewHolder.seamlessIcon.imageTintList = colorList
+            mediaViewHolder.seamlessText.setTextColor(surfaceColor)
         }
     val accentPrimary =
         animatingColorTransitionFactory(
-            loadDefaultColor(R.attr.textColorPrimary),
-            ::accentPrimaryFromScheme
+            if (Flags.mediaControlsUiUpdate()) {
+                buttonBgColor
+            } else {
+                loadDefaultColor(R.attr.textColorPrimary)
+            },
+            ::accentPrimaryFromScheme,
         ) { accentPrimary ->
             val accentColorList = ColorStateList.valueOf(accentPrimary)
             mediaViewHolder.actionPlayPause.backgroundTintList = accentColorList
@@ -145,8 +163,12 @@
 
     val accentSecondary =
         animatingColorTransitionFactory(
-            loadDefaultColor(R.attr.textColorPrimary),
-            ::accentSecondaryFromScheme
+            if (Flags.mediaControlsUiUpdate()) {
+                buttonBgColor
+            } else {
+                loadDefaultColor(R.attr.textColorPrimary)
+            },
+            ::accentSecondaryFromScheme,
         ) { accentSecondary ->
             val colorList = ColorStateList.valueOf(accentSecondary)
             (mediaViewHolder.seamlessButton.background as? RippleDrawable)?.let {
@@ -157,7 +179,11 @@
 
     val colorSeamless =
         animatingColorTransitionFactory(
-            loadDefaultColor(R.attr.textColorPrimary),
+            if (Flags.mediaControlsUiUpdate()) {
+                buttonBgColor
+            } else {
+                loadDefaultColor(R.attr.textColorPrimary)
+            },
             { colorScheme: ColorScheme ->
                 // A1-100 dark in dark theme, A1-200 in light theme
                 if (
@@ -170,13 +196,17 @@
             { seamlessColor: Int ->
                 val accentColorList = ColorStateList.valueOf(seamlessColor)
                 mediaViewHolder.seamlessButton.backgroundTintList = accentColorList
-            }
+            },
         )
 
     val textPrimary =
         animatingColorTransitionFactory(
-            loadDefaultColor(R.attr.textColorPrimary),
-            ::textPrimaryFromScheme
+            if (Flags.mediaControlsUiUpdate()) {
+                textColor
+            } else {
+                loadDefaultColor(R.attr.textColorPrimary)
+            },
+            ::textPrimaryFromScheme,
         ) { textPrimary ->
             mediaViewHolder.titleText.setTextColor(textPrimary)
             val textColorList = ColorStateList.valueOf(textPrimary)
@@ -192,25 +222,41 @@
 
     val textPrimaryInverse =
         animatingColorTransitionFactory(
-            loadDefaultColor(R.attr.textColorPrimaryInverse),
-            ::textPrimaryInverseFromScheme
+            if (Flags.mediaControlsUiUpdate()) {
+                insideButtonColor
+            } else {
+                loadDefaultColor(R.attr.textColorPrimaryInverse)
+            },
+            ::textPrimaryInverseFromScheme,
         ) { textPrimaryInverse ->
-            mediaViewHolder.actionPlayPause.imageTintList =
-                ColorStateList.valueOf(textPrimaryInverse)
+            val colorList = ColorStateList.valueOf(textPrimaryInverse)
+            mediaViewHolder.actionPlayPause.imageTintList = colorList
+
+            if (!Flags.mediaControlsUiUpdate()) return@animatingColorTransitionFactory
+            mediaViewHolder.seamlessIcon.imageTintList = colorList
+            mediaViewHolder.seamlessText.setTextColor(textPrimaryInverse)
         }
 
     val textSecondary =
         animatingColorTransitionFactory(
-            loadDefaultColor(R.attr.textColorSecondary),
-            ::textSecondaryFromScheme
+            if (Flags.mediaControlsUiUpdate()) {
+                textColor
+            } else {
+                loadDefaultColor(R.attr.textColorSecondary)
+            },
+            ::textSecondaryFromScheme,
         ) { textSecondary ->
             mediaViewHolder.artistText.setTextColor(textSecondary)
         }
 
     val textTertiary =
         animatingColorTransitionFactory(
-            loadDefaultColor(R.attr.textColorTertiary),
-            ::textTertiaryFromScheme
+            if (Flags.mediaControlsUiUpdate()) {
+                textColor
+            } else {
+                loadDefaultColor(R.attr.textColorTertiary)
+            },
+            ::textTertiaryFromScheme,
         ) { textTertiary ->
             mediaViewHolder.seekBar.progressBackgroundTintList =
                 ColorStateList.valueOf(textTertiary)
diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginEnablerImpl.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginEnablerImpl.java
index 40f59744..7f1c7a5 100644
--- a/packages/SystemUI/src/com/android/systemui/plugins/PluginEnablerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginEnablerImpl.java
@@ -18,6 +18,7 @@
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
+import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.shared.plugins.PluginEnabler;
@@ -28,6 +29,7 @@
 /** */
 @Singleton
 public class PluginEnablerImpl implements PluginEnabler {
+    private static final String TAG = "PluginEnablerImpl";
     private static final String CRASH_DISABLED_PLUGINS_PREF_FILE = "auto_disabled_plugins_prefs";
 
     private final PackageManager mPm;
@@ -64,8 +66,13 @@
 
     @Override
     public boolean isEnabled(ComponentName component) {
-        return mPm.getComponentEnabledSetting(component)
-                != PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+        try {
+            return mPm.getComponentEnabledSetting(component)
+                    != PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+        } catch (IllegalArgumentException ex) {
+            Log.e(TAG, "Package Manager Exception", ex);
+            return false;
+        }
     }
 
     @Override
@@ -73,6 +80,6 @@
         if (isEnabled(componentName)) {
             return ENABLED;
         }
-        return mAutoDisabledPrefs.getInt(componentName.flattenToString(), DISABLED_MANUALLY);
+        return mAutoDisabledPrefs.getInt(componentName.flattenToString(), DISABLED_UNKNOWN);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/BuildTextView.kt b/packages/SystemUI/src/com/android/systemui/qs/BuildTextView.kt
new file mode 100644
index 0000000..1f864e9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/BuildTextView.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.accessibility.AccessibilityNodeInfo
+import com.android.systemui.res.R
+import com.android.systemui.util.DelayableMarqueeTextView
+
+class BuildTextView
+@JvmOverloads
+constructor(
+    context: Context,
+    attrs: AttributeSet? = null,
+    defStyleAttr: Int = 0,
+    defStyleRes: Int = 0,
+) : DelayableMarqueeTextView(context, attrs, defStyleAttr, defStyleRes) {
+
+    override fun onInitializeAccessibilityNodeInfo(info: AccessibilityNodeInfo?) {
+        super.onInitializeAccessibilityNodeInfo(info)
+        // Clear selected state so it's not announced by accessibility, but we can still marquee.
+        info?.isSelected = false
+        info?.addAction(
+            AccessibilityNodeInfo.AccessibilityAction(
+                AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.id,
+                resources.getString(R.string.copy_to_clipboard_a11y_action),
+            )
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModuleBase.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModuleBase.kt
index 3fd8768..e2a3916 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModuleBase.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModuleBase.kt
@@ -54,11 +54,11 @@
     /** A map of internal QS tiles. Ensures that this can be injected even if it is empty */
     @Multibinds fun tileMap(): Map<String?, QSTileImpl<*>?>?
 
-    @Binds fun bindsQsSceneAdapter(impl: QSSceneAdapterImpl?): QSSceneAdapter?
+    @Binds fun bindsQsSceneAdapter(impl: QSSceneAdapterImpl): QSSceneAdapter
 
     /** Dims the screen */
     @Binds
     fun bindReduceBrightColorsController(
-        impl: ReduceBrightColorsControllerImpl?
-    ): ReduceBrightColorsController?
+        impl: ReduceBrightColorsControllerImpl
+    ): ReduceBrightColorsController
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt
index e1ec338..a6edb58 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt
@@ -27,7 +27,6 @@
 import com.android.systemui.qs.pipeline.domain.autoaddable.DeviceControlsAutoAddable
 import com.android.systemui.qs.pipeline.domain.autoaddable.HotspotAutoAddable
 import com.android.systemui.qs.pipeline.domain.autoaddable.NightDisplayAutoAddable
-import com.android.systemui.qs.pipeline.domain.autoaddable.ReduceBrightColorsAutoAddable
 import com.android.systemui.qs.pipeline.domain.autoaddable.WalletAutoAddable
 import com.android.systemui.qs.pipeline.domain.autoaddable.WorkTileAutoAddable
 import com.android.systemui.qs.pipeline.domain.model.AutoAddable
@@ -45,11 +44,11 @@
         @ElementsIntoSet
         fun providesAutoAddableSetting(
             @Main resources: Resources,
-            autoAddableSettingFactory: AutoAddableSetting.Factory
+            autoAddableSettingFactory: AutoAddableSetting.Factory,
         ): Set<AutoAddable> {
             return AutoAddableSettingList.parseSettingsResource(
                     resources,
-                    autoAddableSettingFactory
+                    autoAddableSettingFactory,
                 )
                 .toSet()
         }
@@ -75,10 +74,6 @@
 
     @Binds @IntoSet fun bindNightDisplayAutoAddable(impl: NightDisplayAutoAddable): AutoAddable
 
-    @Binds
-    @IntoSet
-    fun bindReduceBrightColorsAutoAddable(impl: ReduceBrightColorsAutoAddable): AutoAddable
-
     @Binds @IntoSet fun bindWalletAutoAddable(impl: WalletAutoAddable): AutoAddable
 
     @Binds @IntoSet fun bindWorkModeAutoAddable(impl: WorkTileAutoAddable): AutoAddable
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableList.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableList.kt
index a0c9737..791339c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableList.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableList.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.qs.pipeline.domain.autoaddable
 
-import android.view.accessibility.Flags
 import com.android.internal.accessibility.AccessibilityShortcutController
 import com.android.systemui.qs.pipeline.domain.model.AutoAddable
 import com.android.systemui.qs.pipeline.shared.TileSpec
@@ -33,31 +32,27 @@
      * accessibility features with shortcut options
      */
     fun getA11yShortcutAutoAddables(factory: A11yShortcutAutoAddable.Factory): Set<AutoAddable> {
-        return if (Flags.a11yQsShortcut()) {
-            setOf(
-                factory.create(
-                    TileSpec.create(ColorCorrectionTile.TILE_SPEC),
-                    AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME
-                ),
-                factory.create(
-                    TileSpec.create(ColorInversionTile.TILE_SPEC),
-                    AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME
-                ),
-                factory.create(
-                    TileSpec.create(OneHandedModeTile.TILE_SPEC),
-                    AccessibilityShortcutController.ONE_HANDED_COMPONENT_NAME
-                ),
-                factory.create(
-                    TileSpec.create(ReduceBrightColorsTile.TILE_SPEC),
-                    AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME
-                ),
-                factory.create(
-                    TileSpec.create(HearingDevicesTile.TILE_SPEC),
-                    AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME
-                )
-            )
-        } else {
-            emptySet()
-        }
+        return setOf(
+            factory.create(
+                TileSpec.create(ColorCorrectionTile.TILE_SPEC),
+                AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME,
+            ),
+            factory.create(
+                TileSpec.create(ColorInversionTile.TILE_SPEC),
+                AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME,
+            ),
+            factory.create(
+                TileSpec.create(OneHandedModeTile.TILE_SPEC),
+                AccessibilityShortcutController.ONE_HANDED_COMPONENT_NAME,
+            ),
+            factory.create(
+                TileSpec.create(ReduceBrightColorsTile.TILE_SPEC),
+                AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME,
+            ),
+            factory.create(
+                TileSpec.create(HearingDevicesTile.TILE_SPEC),
+                AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME,
+            ),
+        )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddable.kt
deleted file mode 100644
index bde6820..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddable.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs.pipeline.domain.autoaddable
-
-import android.view.accessibility.Flags
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.qs.ReduceBrightColorsController
-import com.android.systemui.qs.dagger.QSFlagsModule.RBC_AVAILABLE
-import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
-import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
-import com.android.systemui.qs.pipeline.shared.TileSpec
-import com.android.systemui.qs.tiles.ReduceBrightColorsTile
-import javax.inject.Inject
-import javax.inject.Named
-import kotlinx.coroutines.channels.ProducerScope
-
-/**
- * [AutoAddable] for [ReduceBrightColorsTile.TILE_SPEC].
- *
- * It will send a signal to add the tile when reduce bright colors is enabled.
- */
-@SysUISingleton
-class ReduceBrightColorsAutoAddable
-@Inject
-constructor(
-    controller: ReduceBrightColorsController,
-    @Named(RBC_AVAILABLE) private var available: Boolean,
-) :
-    CallbackControllerAutoAddable<
-        ReduceBrightColorsController.Listener,
-        ReduceBrightColorsController,
-    >(controller) {
-
-    override val spec: TileSpec
-        get() = TileSpec.create(ReduceBrightColorsTile.TILE_SPEC)
-
-    override fun ProducerScope<AutoAddSignal>.getCallback(): ReduceBrightColorsController.Listener {
-        return object : ReduceBrightColorsController.Listener {
-            override fun onActivated(activated: Boolean) {
-                if (activated && available) {
-                    sendAdd()
-                }
-            }
-        }
-    }
-
-    override val autoAddTracking
-        get() =
-            if (Flags.a11yQsShortcut()) {
-                AutoAddTracking.Disabled
-            } else if (available) {
-                super.autoAddTracking
-            } else {
-                AutoAddTracking.Disabled
-            }
-
-    override val description = "ReduceBrightColorsAutoAddable ($autoAddTracking)"
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AccessibilityTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AccessibilityTilesInteractor.kt
index 56c3e0e..481d1e3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AccessibilityTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AccessibilityTilesInteractor.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.qs.pipeline.domain.interactor
 
 import android.content.Context
-import android.view.accessibility.Flags
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.accessibility.data.repository.AccessibilityQsShortcutsRepository
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -30,7 +30,6 @@
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.collectLatest
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /** Observe the tiles in the QS Panel and perform accessibility related actions */
 @SysUISingleton
@@ -48,10 +47,7 @@
         if (!initialized.compareAndSet(/* expectedValue= */ false, /* newValue= */ true)) {
             return
         }
-
-        if (Flags.a11yQsShortcut()) {
-            startObservingTiles(currentTilesInteractor)
-        }
+        startObservingTiles(currentTilesInteractor)
     }
 
     private fun startObservingTiles(currentTilesInteractor: CurrentTilesInteractor) {
@@ -63,14 +59,11 @@
                 .collectLatest {
                     a11yQsShortcutsRepository.notifyAccessibilityManagerTilesChanged(
                         it.userContext,
-                        it.currentTileSpecs
+                        it.currentTileSpecs,
                     )
                 }
         }
     }
 
-    private data class Data(
-        val currentTileSpecs: List<TileSpec>,
-        val userContext: Context,
-    )
+    private data class Data(val currentTileSpecs: List<TileSpec>, val userContext: Context)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java
index 74563ff..22b742f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java
@@ -29,7 +29,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.Flags;
 import com.android.systemui.accessibility.hearingaid.HearingDevicesChecker;
 import com.android.systemui.accessibility.hearingaid.HearingDevicesDialogManager;
 import com.android.systemui.animation.Expandable;
@@ -138,9 +137,4 @@
     public CharSequence getTileLabel() {
         return mContext.getString(R.string.quick_settings_hearing_devices_label);
     }
-
-    @Override
-    public boolean isAvailable() {
-        return Flags.hearingAidsQsTileDialog();
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileDataInteractor.kt
index ec0a4e9..33b7feb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileDataInteractor.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.qs.tiles.impl.hearingdevices.domain.interactor
 
 import android.os.UserHandle
-import com.android.systemui.Flags
 import com.android.systemui.accessibility.hearingaid.HearingDevicesChecker
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
@@ -62,8 +61,7 @@
             .flowOn(backgroundContext)
             .distinctUntilChanged()
 
-    override fun availability(user: UserHandle): Flow<Boolean> =
-        flowOf(Flags.hearingAidsQsTileDialog())
+    override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true)
 
     private fun getModel() =
         HearingDevicesTileModel(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt
index c0c0aea..465d08b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt
@@ -17,13 +17,14 @@
 package com.android.systemui.qs.ui.viewmodel
 
 import androidx.compose.runtime.getValue
-import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.lifecycle.Hydrator
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
+import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
+import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import kotlinx.coroutines.awaitCancellation
@@ -44,6 +45,7 @@
 constructor(
     val shadeInteractor: ShadeInteractor,
     val sceneInteractor: SceneInteractor,
+    val notificationStackAppearanceInteractor: NotificationStackAppearanceInteractor,
     val shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory,
     quickSettingsContainerViewModelFactory: QuickSettingsContainerViewModel.Factory,
 ) : ExclusiveActivatable() {
@@ -92,6 +94,11 @@
         awaitCancellation()
     }
 
+    /** Notifies that the bounds of the QuickSettings panel have changed. */
+    fun onPanelShapeChanged(shape: ShadeScrimShape?) {
+        notificationStackAppearanceInteractor.setQsPanelShape(shape)
+    }
+
     fun onScrimClicked() {
         shadeInteractor.collapseQuickSettingsShade(loggingReason = "shade scrim clicked")
     }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 3a23a71..8657c172 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -94,7 +94,6 @@
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.distinctUntilChangedBy
-import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.filterIsInstance
 import kotlinx.coroutines.flow.filterNot
@@ -717,14 +716,7 @@
         }
 
         applicationScope.launch {
-            keyguardInteractor.isAodAvailable
-                .flatMapLatest { isAodAvailable ->
-                    if (!isAodAvailable) {
-                        powerInteractor.detailedWakefulness
-                    } else {
-                        emptyFlow()
-                    }
-                }
+            powerInteractor.detailedWakefulness
                 .distinctUntilChangedBy { it.isAwake() }
                 .collect { wakefulness ->
                     when {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/OWNERS b/packages/SystemUI/src/com/android/systemui/shade/OWNERS
index 5b82772..89454b8 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/shade/OWNERS
@@ -1,5 +1,7 @@
 justinweir@google.com
 syeonlee@google.com
+nicomazz@google.com
+burakov@google.com
 
 per-file *Notification* = set noparent
 per-file *Notification* = file:../statusbar/notification/OWNERS
@@ -9,13 +11,13 @@
 per-file *ShadeHeader* = syeonlee@google.com, kozynski@google.com, asc@google.com
 
 per-file *Interactor* = set noparent
-per-file *Interactor* = justinweir@google.com, syeonlee@google.com, nijamkin@google.com
+per-file *Interactor* = justinweir@google.com, syeonlee@google.com, nijamkin@google.com, nicomazz@google.com, burakov@google.com
 per-file *Repository* = set noparent
-per-file *Repository* = justinweir@google.com, syeonlee@google.com, nijamkin@google.com
+per-file *Repository* = justinweir@google.com, syeonlee@google.com, nijamkin@google.com, nicomazz@google.com, burakov@google.com
 
-per-file NotificationShadeWindow* = pixel@google.com, cinek@google.com, juliacr@google.com, justinweir@google.com, syeonlee@google.com
+per-file NotificationShadeWindow* = pixel@google.com, cinek@google.com, juliacr@google.com, justinweir@google.com, syeonlee@google.com, nicomazz@google.com, burakov@google.com
 
 per-file NotificationPanelUnfoldAnimationController.kt = alexflo@google.com, jeffdq@google.com, juliacr@google.com
 
-per-file NotificationPanelView.java = pixel@google.com, cinek@google.com, juliacr@google.com, justinweir@google.com, syeonlee@google.com
-per-file NotificationPanelViewController.java = pixel@google.com, cinek@google.com, juliacr@google.com, justinweir@google.com, syeonlee@google.com
+per-file NotificationPanelView.java = pixel@google.com, cinek@google.com, juliacr@google.com, justinweir@google.com, syeonlee@google.com, nicomazz@google.com, burakov@google.com
+per-file NotificationPanelViewController.java = pixel@google.com, cinek@google.com, juliacr@google.com, justinweir@google.com, syeonlee@google.com, nicomazz@google.com, burakov@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayChangeLatencyTracker.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayChangeLatencyTracker.kt
index e358dce..ec9bba7 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayChangeLatencyTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayChangeLatencyTracker.kt
@@ -95,7 +95,7 @@
      */
     @Synchronized
     fun onShadeDisplayChanging(displayId: Int) {
-        previousJob?.cancel(CancellationException("New shade move in progress"))
+        previousJob?.cancel(CancellationException("New shade move in progress to $displayId"))
         previousJob = bgScope.launch { onShadeDisplayChangingAsync(displayId) }
     }
 
@@ -109,8 +109,8 @@
             val reason =
                 when (e) {
                     is CancellationException ->
-                        "Shade move cancelled as a new move is being done " +
-                            "before the previous one finished."
+                        "Shade move to $displayId cancelled as a new move is being done " +
+                            "before the previous one finished. Message: ${e.message}"
 
                     else -> "Shade move cancelled."
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt b/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt
index 17b5e5b..d53f9f7 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.shade.display
 
+import com.android.systemui.shade.domain.interactor.ShadeExpandedStateInteractor.ShadeElement
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.IntoSet
@@ -33,11 +34,33 @@
     val displayId: StateFlow<Int>
 }
 
+/** Return the latest element the user intended to expand in the shade (notifications or QS). */
+interface ShadeExpansionIntent {
+    /**
+     * Returns the latest element the user intended to expand in the shade (notifications or QS).
+     *
+     * When the shade moves to a different display (e.g., due to a touch on the status bar of an
+     * external display), it's first collapsed and then re-expanded on the target display.
+     *
+     * If the user was trying to open a specific element (QS or notifications) when the shade was on
+     * the original display, that intention might be lost during the collapse/re-expand transition.
+     * This is used to preserve the user's intention, ensuring the correct element is expanded on
+     * the target display.
+     *
+     * Note that the expansion intent is kept for a very short amount of time (ideally, just a bit
+     * above the time it takes for the shade to collapse)
+     */
+    fun consumeExpansionIntent(): ShadeElement?
+}
+
 @Module
 interface ShadeDisplayPolicyModule {
 
     @Binds fun provideDefaultPolicy(impl: StatusBarTouchShadeDisplayPolicy): ShadeDisplayPolicy
 
+    @Binds
+    fun provideShadeExpansionIntent(impl: StatusBarTouchShadeDisplayPolicy): ShadeExpansionIntent
+
     @IntoSet
     @Binds
     fun provideDefaultDisplayPolicyToSet(impl: DefaultDisplayShadePolicy): ShadeDisplayPolicy
diff --git a/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt b/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt
index 30b086f..91020aa 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt
@@ -18,16 +18,25 @@
 
 import android.util.Log
 import android.view.Display
+import android.view.MotionEvent
 import com.android.app.tracing.coroutines.launchTraced
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.display.data.repository.DisplayRepository
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.shade.ShadeOnDefaultDisplayWhenLocked
+import com.android.systemui.shade.domain.interactor.NotificationShadeElement
+import com.android.systemui.shade.domain.interactor.QSShadeElement
+import com.android.systemui.shade.domain.interactor.ShadeExpandedStateInteractor.ShadeElement
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
+import dagger.Lazy
+import java.util.concurrent.atomic.AtomicReference
 import javax.inject.Inject
+import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
@@ -49,14 +58,20 @@
 constructor(
     displayRepository: DisplayRepository,
     keyguardRepository: KeyguardRepository,
-    @Background val backgroundScope: CoroutineScope,
-    @ShadeOnDefaultDisplayWhenLocked val shadeOnDefaultDisplayWhenLocked: Boolean,
-) : ShadeDisplayPolicy {
+    @Background private val backgroundScope: CoroutineScope,
+    @ShadeOnDefaultDisplayWhenLocked private val shadeOnDefaultDisplayWhenLocked: Boolean,
+    private val shadeInteractor: Lazy<ShadeInteractor>,
+    private val qsShadeElement: Lazy<QSShadeElement>,
+    private val notificationElement: Lazy<NotificationShadeElement>,
+) : ShadeDisplayPolicy, ShadeExpansionIntent {
     override val name: String = "status_bar_latest_touch"
 
     private val currentDisplayId = MutableStateFlow(Display.DEFAULT_DISPLAY)
     private val availableDisplayIds: StateFlow<Set<Int>> = displayRepository.displayIds
 
+    private var latestIntent = AtomicReference<ShadeElement?>()
+    private var timeoutJob: Job? = null
+
     override val displayId: StateFlow<Int> =
         if (shadeOnDefaultDisplayWhenLocked) {
             keyguardRepository.isKeyguardShowing
@@ -75,8 +90,29 @@
     private var removalListener: Job? = null
 
     /** Called when the status bar on the given display is touched. */
-    fun onStatusBarTouched(statusBarDisplayId: Int) {
+    fun onStatusBarTouched(event: MotionEvent, statusBarWidth: Int) {
         ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()
+        updateShadeDisplayIfNeeded(event)
+        updateExpansionIntent(event, statusBarWidth)
+    }
+
+    override fun consumeExpansionIntent(): ShadeElement? {
+        return latestIntent.getAndSet(null)
+    }
+
+    private fun updateExpansionIntent(event: MotionEvent, statusBarWidth: Int) {
+        val element = classifyStatusBarEvent(event, statusBarWidth)
+        latestIntent.set(element)
+        timeoutJob?.cancel()
+        timeoutJob =
+            backgroundScope.launchTraced("StatusBarTouchDisplayPolicy#intentTimeout") {
+                delay(EXPANSION_INTENT_EXPIRY)
+                latestIntent.set(null)
+            }
+    }
+
+    private fun updateShadeDisplayIfNeeded(event: MotionEvent) {
+        val statusBarDisplayId = event.displayId
         if (statusBarDisplayId !in availableDisplayIds.value) {
             Log.e(TAG, "Got touch on unknown display $statusBarDisplayId")
             return
@@ -90,6 +126,17 @@
         }
     }
 
+    private fun classifyStatusBarEvent(
+        motionEvent: MotionEvent,
+        statusbarWidth: Int,
+    ): ShadeElement {
+        val xPercentage = motionEvent.x / statusbarWidth
+        val threshold = shadeInteractor.get().getTopEdgeSplitFraction()
+        return if (xPercentage < threshold) {
+            notificationElement.get()
+        } else qsShadeElement.get()
+    }
+
     private fun monitorDisplayRemovals(): Job {
         return backgroundScope.launchTraced("StatusBarTouchDisplayPolicy#monitorDisplayRemovals") {
             currentDisplayId.subscriptionCount
@@ -112,5 +159,6 @@
 
     private companion object {
         const val TAG = "StatusBarTouchDisplayPolicy"
+        val EXPANSION_INTENT_EXPIRY = 2.seconds
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
index 691a383..fc26499 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.shade.ShadeTraceLogger.logMoveShadeWindowTo
 import com.android.systemui.shade.ShadeTraceLogger.traceReparenting
 import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
+import com.android.systemui.shade.display.ShadeExpansionIntent
 import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
 import com.android.window.flags.Flags
 import java.util.Optional
@@ -49,6 +50,7 @@
     @Main private val mainThreadContext: CoroutineContext,
     private val shadeDisplayChangeLatencyTracker: ShadeDisplayChangeLatencyTracker,
     shadeExpandedInteractor: Optional<ShadeExpandedStateInteractor>,
+    private val shadeExpansionIntent: ShadeExpansionIntent,
 ) : CoreStartable {
 
     private val shadeExpandedInteractor =
@@ -89,11 +91,10 @@
         try {
             withContext(mainThreadContext) {
                 traceReparenting {
-                    shadeDisplayChangeLatencyTracker.onShadeDisplayChanging(destinationId)
-                    val expandedElement = shadeExpandedInteractor.currentlyExpandedElement.value
-                    expandedElement?.collapse(reason = "Shade window move")
-                    reparentToDisplayId(id = destinationId)
-                    expandedElement?.expand(reason = "Shade window move")
+                    collapseAndExpandShadeIfNeeded {
+                        shadeDisplayChangeLatencyTracker.onShadeDisplayChanging(destinationId)
+                        reparentToDisplayId(id = destinationId)
+                    }
                     checkContextDisplayMatchesExpected(destinationId)
                 }
             }
@@ -106,6 +107,18 @@
         }
     }
 
+    private suspend fun collapseAndExpandShadeIfNeeded(wrapped: () -> Unit) {
+        val previouslyExpandedElement = shadeExpandedInteractor.currentlyExpandedElement.value
+        previouslyExpandedElement?.collapse(reason = COLLAPSE_EXPAND_REASON)
+
+        wrapped()
+
+        // If the user was trying to expand a specific shade element, let's make sure to expand
+        // that one. Otherwise, we can just re-expand the previous expanded element.
+        shadeExpansionIntent.consumeExpansionIntent()?.expand(COLLAPSE_EXPAND_REASON)
+            ?: previouslyExpandedElement?.expand(reason = COLLAPSE_EXPAND_REASON)
+    }
+
     private fun checkContextDisplayMatchesExpected(destinationId: Int) {
         if (shadeContext.displayId != destinationId) {
             Log.wtf(
@@ -125,5 +138,6 @@
 
     private companion object {
         const val TAG = "ShadeDisplaysInteractor"
+        const val COLLAPSE_EXPAND_REASON = "Shade window move"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeExpandedStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeExpandedStateInteractor.kt
index dd3abee..aba5a6b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeExpandedStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeExpandedStateInteractor.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.shade.domain.interactor
 
+import android.util.Log
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
@@ -24,6 +25,8 @@
 import com.android.systemui.shade.shared.flag.DualShade
 import com.android.systemui.util.kotlin.Utils.Companion.combineState
 import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlin.time.Duration
 import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -31,7 +34,7 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.withContext
-import kotlinx.coroutines.withTimeout
+import kotlinx.coroutines.withTimeoutOrNull
 
 /**
  * Wrapper around [ShadeInteractor] to facilitate expansion and collapse of Notifications and quick
@@ -47,7 +50,7 @@
     val currentlyExpandedElement: StateFlow<ShadeElement?>
 
     /** An element from the shade window that can be expanded or collapsed. */
-    abstract class ShadeElement {
+    sealed class ShadeElement {
         /** Expands the shade element, returning when the expansion is done */
         abstract suspend fun expand(reason: String)
 
@@ -56,17 +59,18 @@
     }
 }
 
+private val EXPAND_COLLAPSE_TIMEOUT: Duration = 1.seconds
+
 @SysUISingleton
 class ShadeExpandedStateInteractorImpl
 @Inject
 constructor(
-    private val shadeInteractor: ShadeInteractor,
+    shadeInteractor: ShadeInteractor,
     @Background private val bgScope: CoroutineScope,
+    private val notificationElement: NotificationShadeElement,
+    private val qsElement: QSShadeElement,
 ) : ShadeExpandedStateInteractor {
 
-    private val notificationElement = NotificationElement()
-    private val qsElement = QSElement()
-
     override val currentlyExpandedElement: StateFlow<ShadeElement?> =
         if (SceneContainerFlag.isEnabled) {
             combineState(
@@ -84,35 +88,54 @@
         } else {
             MutableStateFlow(null)
         }
+}
 
-    inner class NotificationElement : ShadeElement() {
-        override suspend fun expand(reason: String) {
-            shadeInteractor.expandNotificationsShade(reason)
-            shadeInteractor.shadeExpansion.waitUntil(1f)
+private suspend fun StateFlow<Float>.waitUntil(f: Float, coroutineContext: CoroutineContext) {
+    // it's important to not do this in the main thread otherwise it will block any rendering.
+    withContext(coroutineContext) {
+        withTimeoutOrNull(EXPAND_COLLAPSE_TIMEOUT) {
+            traceWaitForExpansion(expansion = f) { first { it == f } }
         }
+            ?: Log.e(
+                "ShadeExpStateInteractor",
+                "Timed out after ${EXPAND_COLLAPSE_TIMEOUT.inWholeMilliseconds}ms while waiting " +
+                        "for expansion to match $f. Current one: $value",
+            )
+    }
+}
 
-        override suspend fun collapse(reason: String) {
-            shadeInteractor.collapseNotificationsShade(reason)
-            shadeInteractor.shadeExpansion.waitUntil(0f)
-        }
+@SysUISingleton
+class NotificationShadeElement
+@Inject
+constructor(
+    private val shadeInteractor: ShadeInteractor,
+    @Background private val bgContext: CoroutineContext,
+) : ShadeElement() {
+    override suspend fun expand(reason: String) {
+        shadeInteractor.expandNotificationsShade(reason)
+        shadeInteractor.shadeExpansion.waitUntil(1f, bgContext)
     }
 
-    inner class QSElement : ShadeElement() {
-        override suspend fun expand(reason: String) {
-            shadeInteractor.expandQuickSettingsShade(reason)
-            shadeInteractor.qsExpansion.waitUntil(1f)
-        }
+    override suspend fun collapse(reason: String) {
+        shadeInteractor.collapseNotificationsShade(reason)
+        shadeInteractor.shadeExpansion.waitUntil(0f, bgContext)
+    }
+}
 
-        override suspend fun collapse(reason: String) {
-            shadeInteractor.collapseQuickSettingsShade(reason)
-            shadeInteractor.qsExpansion.waitUntil(0f)
-        }
+@SysUISingleton
+class QSShadeElement
+@Inject
+constructor(
+    private val shadeInteractor: ShadeInteractor,
+    @Background private val bgContext: CoroutineContext,
+) : ShadeElement() {
+    override suspend fun expand(reason: String) {
+        shadeInteractor.expandQuickSettingsShade(reason)
+        shadeInteractor.qsExpansion.waitUntil(1f, bgContext)
     }
 
-    private suspend fun StateFlow<Float>.waitUntil(f: Float) {
-        // it's important to not do this in the main thread otherwise it will block any rendering.
-        withContext(bgScope.coroutineContext) {
-            withTimeout(1.seconds) { traceWaitForExpansion(expansion = f) { first { it == f } } }
-        }
+    override suspend fun collapse(reason: String) {
+        shadeInteractor.collapseQuickSettingsShade(reason)
+        shadeInteractor.qsExpansion.waitUntil(0f, bgContext)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java b/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
index 5ef5a7d..af51c49 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
@@ -284,6 +284,7 @@
                 | WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
                 | WindowManager.LayoutParams.PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW;
         lp.setTitle("ImmersiveModeConfirmation");
+        lp.accessibilityTitle = mSysUiContext.getString(R.string.immersive_cling_title);
         lp.windowAnimations = com.android.internal.R.style.Animation_ImmersiveModeConfirmation;
         lp.token = getWindowToken();
         return lp;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarConntectedDisplays.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarConnectedDisplays.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarConntectedDisplays.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarConnectedDisplays.kt
index 54a18f7..06474b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarConntectedDisplays.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarConnectedDisplays.kt
@@ -16,9 +16,9 @@
 
 package com.android.systemui.statusbar.core
 
-import com.android.systemui.Flags
 import com.android.systemui.flags.FlagToken
 import com.android.systemui.flags.RefactorFlagUtils
+import com.android.systemui.shared.Flags
 
 /** Helper for reading or using the status bar connected displays flag state. */
 @Suppress("NOTHING_TO_INLINE")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarRootModernization.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarRootModernization.kt
index 057213f..3c30f3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarRootModernization.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarRootModernization.kt
@@ -25,6 +25,9 @@
     /** Aconfig flag for removing the fragment */
     const val FLAG_NAME = Flags.FLAG_STATUS_BAR_ROOT_MODERNIZATION
 
+    /** Shows a "compose->bar" text in the status bar for debug purposes */
+    const val SHOW_DISAMBIGUATION = false
+
     /** A token used for dependency declaration */
     val token: FlagToken
         get() = FlagToken(FLAG_NAME, isEnabled)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index ea48fb4..086c32c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -60,7 +60,6 @@
 import com.android.systemui.statusbar.notification.data.NotificationDataLayerModule;
 import com.android.systemui.statusbar.notification.domain.NotificationDomainLayerModule;
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor;
-import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModelModule;
 import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
 import com.android.systemui.statusbar.notification.icon.ConversationIconManager;
 import com.android.systemui.statusbar.notification.icon.IconManager;
@@ -109,7 +108,6 @@
  * Dagger Module for classes found within the com.android.systemui.statusbar.notification package.
  */
 @Module(includes = {
-        FooterViewModelModule.class,
         KeyguardNotificationVisibilityProviderModule.class,
         NotificationDataLayerModule.class,
         NotificationDomainLayerModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index a670f69..c88dd7a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
@@ -40,6 +40,7 @@
 import androidx.annotation.NonNull;
 
 import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.statusbar.notification.ColorUpdateLogger;
 import com.android.systemui.statusbar.notification.footer.shared.NotifRedesignFooter;
 import com.android.systemui.statusbar.notification.row.FooterViewButton;
@@ -333,11 +334,9 @@
 
     /**
      * Whether the touch is outside the Clear all button.
-     *
-     * TODO(b/293167744): This is an artifact from the time when we could press underneath the
-     * shade to dismiss it. Check if it's safe to remove.
      */
     public boolean isOnEmptySpace(float touchX, float touchY) {
+        SceneContainerFlag.assertInLegacyMode();
         return touchX < mContent.getX()
                 || touchX > mContent.getX() + mContent.getWidth()
                 || touchY < mContent.getY()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
index 5696e9f..c895c419 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
@@ -19,7 +19,6 @@
 import android.content.Intent
 import android.provider.Settings
 import com.android.internal.jank.InteractionJankMonitor
-import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.res.R
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor
@@ -32,10 +31,8 @@
 import com.android.systemui.util.ui.AnimatableEvent
 import com.android.systemui.util.ui.AnimatedValue
 import com.android.systemui.util.ui.toAnimatedValueFlow
-import dagger.Module
-import dagger.Provides
-import java.util.Optional
-import javax.inject.Provider
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
@@ -44,7 +41,9 @@
 import kotlinx.coroutines.flow.onStart
 
 /** ViewModel for [FooterView]. */
-class FooterViewModel(
+class FooterViewModel
+@AssistedInject
+constructor(
     activeNotificationsInteractor: ActiveNotificationsInteractor,
     notificationSettingsInteractor: NotificationSettingsInteractor,
     seenNotificationsInteractor: SeenNotificationsInteractor,
@@ -141,25 +140,9 @@
                     AnimatedValue.NotAnimating(!messageVisible)
                 },
         )
-}
 
-// TODO: b/293167744 - remove this, use new viewmodel style
-@Module
-object FooterViewModelModule {
-    @Provides
-    @SysUISingleton
-    fun provideOptional(
-        activeNotificationsInteractor: Provider<ActiveNotificationsInteractor>,
-        notificationSettingsInteractor: Provider<NotificationSettingsInteractor>,
-        seenNotificationsInteractor: Provider<SeenNotificationsInteractor>,
-        shadeInteractor: Provider<ShadeInteractor>,
-    ): Optional<FooterViewModel> =
-        Optional.of(
-            FooterViewModel(
-                activeNotificationsInteractor.get(),
-                notificationSettingsInteractor.get(),
-                seenNotificationsInteractor.get(),
-                shadeInteractor.get(),
-            )
-        )
+    @AssistedFactory
+    interface Factory {
+        fun create(): FooterViewModel
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 70e27a9..7b3a471 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -504,6 +504,7 @@
         CharSequence redactedMessage = systemUiContext.getString(
                 R.string.redacted_notification_single_line_text
         );
+        redacted.setWhen(original.getWhen());
 
         if (originalStyle instanceof MessagingStyle oldStyle) {
             MessagingStyle newStyle = new MessagingStyle(oldStyle.getUser());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt
index 98d704c..f3ee34f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt
@@ -19,6 +19,7 @@
 import android.annotation.WorkerThread
 import android.app.ActivityManager
 import android.app.Flags
+import android.app.Flags.notificationsRedesignThemedAppIcons
 import android.content.Context
 import android.content.pm.PackageManager.NameNotFoundException
 import android.graphics.Color
@@ -29,6 +30,8 @@
 import com.android.internal.R
 import com.android.launcher3.icons.BaseIconFactory
 import com.android.launcher3.icons.BaseIconFactory.IconOptions
+import com.android.launcher3.icons.BitmapInfo
+import com.android.launcher3.icons.mono.MonoIconThemeController
 import com.android.launcher3.util.UserIconInfo
 import com.android.systemui.Dumpable
 import com.android.systemui.dagger.SysUISingleton
@@ -55,6 +58,7 @@
         packageName: String,
         context: Context,
         withWorkProfileBadge: Boolean = false,
+        themed: Boolean = true,
     ): Drawable
 
     /**
@@ -74,6 +78,17 @@
         dumpManager.registerNormalDumpable(TAG, this)
     }
 
+    private class NotificationIcons(context: Context?, fillResIconDpi: Int, iconBitmapSize: Int) :
+        BaseIconFactory(context, fillResIconDpi, iconBitmapSize) {
+
+        init {
+            if (notificationsRedesignThemedAppIcons()) {
+                // Initialize the controller so that we can support themed icons.
+                mThemeController = MonoIconThemeController()
+            }
+        }
+    }
+
     private val iconFactory: BaseIconFactory
         get() {
             val isLowRam = ActivityManager.isLowRamDeviceStatic()
@@ -83,7 +98,7 @@
                     if (isLowRam) R.dimen.notification_small_icon_size_low_ram
                     else R.dimen.notification_small_icon_size
                 )
-            return BaseIconFactory(sysuiContext, res.configuration.densityDpi, iconSize)
+            return NotificationIcons(sysuiContext, res.configuration.densityDpi, iconSize)
         }
 
     private val cache = NotifCollectionCache<Drawable>()
@@ -92,12 +107,15 @@
         packageName: String,
         context: Context,
         withWorkProfileBadge: Boolean,
+        themed: Boolean,
     ): Drawable {
         // Add a suffix to distinguish the app installed on the work profile, since the icon will
         // be different.
         val key = packageName + if (withWorkProfileBadge) WORK_SUFFIX else ""
 
-        return cache.getOrFetch(key) { fetchAppIcon(packageName, context, withWorkProfileBadge) }
+        return cache.getOrFetch(key) {
+            fetchAppIcon(packageName, context, withWorkProfileBadge, themed)
+        }
     }
 
     @WorkerThread
@@ -105,6 +123,7 @@
         packageName: String,
         context: Context,
         withWorkProfileBadge: Boolean,
+        themed: Boolean,
     ): Drawable {
         val pm = context.packageManager
         val icon = pm.getApplicationInfo(packageName, 0).loadUnbadgedIcon(pm)
@@ -113,13 +132,14 @@
             IconOptions().apply {
                 setUser(userIconInfo(context, withWorkProfileBadge))
                 setBitmapGenerationMode(BaseIconFactory.MODE_HARDWARE)
-                // This color is not used since we're not showing the themed icons. We're just
-                // setting it so that the icon factory doesn't try to extract colors from our bitmap
-                // (since it won't work, given it's a hardware bitmap).
+                // This color will not be used, but we're just setting it so that the icon factory
+                // doesn't try to extract colors from our bitmap (since it won't work, given it's a
+                // hardware bitmap).
                 setExtractedColor(Color.BLUE)
             }
         val badgedIcon = iconFactory.createBadgedIconBitmap(icon, options)
-        return badgedIcon.newIcon(sysuiContext)
+        val creationFlags = if (themed) BitmapInfo.FLAG_THEMED else 0
+        return badgedIcon.newIcon(sysuiContext, creationFlags)
     }
 
     private fun userIconInfo(context: Context, withWorkProfileBadge: Boolean): UserIconInfo {
@@ -165,6 +185,7 @@
         packageName: String,
         context: Context,
         withWorkProfileBadge: Boolean,
+        themed: Boolean,
     ): Drawable {
         Log.wtf(TAG, "NoOpIconProvider should not be used anywhere.")
         return ColorDrawable(Color.WHITE)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt
index 64fdf6f..bb4aa86 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.row.icon
 
+import android.app.Flags.notificationsRedesignThemedAppIcons
 import android.content.Context
 import android.graphics.drawable.Drawable
 import android.util.AttributeSet
@@ -74,6 +75,7 @@
                     sbn.packageName,
                     context,
                     withWorkProfileBadge,
+                    themed = notificationsRedesignThemedAppIcons(),
                 )
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 76591ac..a8a1318 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -512,23 +512,27 @@
      */
     private final Path mRoundedClipPath = new Path();
 
+    /** The clip path defining where we are NOT allowed to draw. */
+    private final Path mNegativeRoundedClipPath = new Path();
+
     /**
      * The clip Path used to clip the launching notification. This may be different
      * from the normal path, as the views launch animation could start clipped.
      */
     private final Path mLaunchedNotificationClipPath = new Path();
 
-    /**
-     * Should we use rounded rect clipping right now
-     */
+    /** Should we use rounded rect clipping right now */
     private boolean mShouldUseRoundedRectClipping = false;
 
+    /** Should we set an out path for the drawing canvas */
+    private boolean mShouldUseNegativeRoundedRectClipping = false;
+
     private int mRoundedRectClippingLeft;
     private int mRoundedRectClippingTop;
     private int mRoundedRectClippingBottom;
     private int mRoundedRectClippingRight;
     private int mRoundedRectClippingYTranslation;
-    private final float[] mBgCornerRadii = new float[8];
+    private final float[] mRoundedClipCornerRadii = new float[8];
 
     /**
      * Whether stackY should be animated in case the view is getting shorter than the scroll
@@ -1245,15 +1249,19 @@
     @Override
     public void setHeadsUpTop(float headsUpTop) {
         if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
-        mAmbientState.setHeadsUpTop(headsUpTop);
-        requestChildrenUpdate();
+        if (mAmbientState.getHeadsUpTop() != headsUpTop) {
+            mAmbientState.setHeadsUpTop(headsUpTop);
+            requestChildrenUpdate();
+        }
     }
 
     @Override
     public void setHeadsUpBottom(float headsUpBottom) {
         if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
-        mAmbientState.setHeadsUpBottom(headsUpBottom);
-        mStateAnimator.setHeadsUpAppearHeightBottom(Math.round(headsUpBottom));
+        if (mAmbientState.getHeadsUpBottom() != headsUpBottom) {
+            mAmbientState.setHeadsUpBottom(headsUpBottom);
+            mStateAnimator.setHeadsUpAppearHeightBottom(Math.round(headsUpBottom));
+        }
     }
 
     @Override
@@ -3860,7 +3868,7 @@
         if (!SceneContainerFlag.isEnabled()) {
             return !isInsideQsHeader(ev);
         }
-        ShadeScrimShape shape = mScrollViewFields.getScrimClippingShape();
+        ShadeScrimShape shape = mScrollViewFields.getClippingShape();
         if (shape == null) {
             return true; // When there is no scrim, consider this event scrollable.
         }
@@ -5386,7 +5394,8 @@
             println(pw, "pulsing", mPulsing);
             println(pw, "expanded", mIsExpanded);
             println(pw, "headsUpPinned", mInHeadsUpPinnedMode);
-            println(pw, "qsClipping", mShouldUseRoundedRectClipping);
+            println(pw, "roundedRectClipping", mShouldUseRoundedRectClipping);
+            println(pw, "negativeRoundedRectClipping", mShouldUseNegativeRoundedRectClipping);
             println(pw, "qsClipDismiss", mDismissUsingRowTranslationX);
             println(pw, "visibility", visibilityString(getVisibility()));
             println(pw, "alpha", getAlpha());
@@ -5465,8 +5474,8 @@
         pw.append(" r=").print(mRoundedRectClippingRight);
         pw.append(" b=").print(mRoundedRectClippingBottom);
         pw.append(" +y=").print(mRoundedRectClippingYTranslation);
-        pw.append("} topRadius=").print(mBgCornerRadii[0]);
-        pw.append(" bottomRadius=").println(mBgCornerRadii[4]);
+        pw.append("} topRadius=").print(mRoundedClipCornerRadii[0]);
+        pw.append(" bottomRadius=").println(mRoundedClipCornerRadii[4]);
     }
 
     public boolean isFullyHidden() {
@@ -5913,14 +5922,12 @@
         mScrollListener = listener;
     }
 
-    /**
-     * Set rounded rect clipping bounds on this view.
-     */
+    /** Sets rounded rect where the view is allowed to draw. */
     @Override
-    public void setScrimClippingShape(@Nullable ShadeScrimShape shape) {
+    public void setClippingShape(@Nullable ShadeScrimShape shape) {
         if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
-        if (Objects.equals(mScrollViewFields.getScrimClippingShape(), shape)) return;
-        mScrollViewFields.setScrimClippingShape(shape);
+        if (Objects.equals(mScrollViewFields.getClippingShape(), shape)) return;
+        mScrollViewFields.setClippingShape(shape);
         mShouldUseRoundedRectClipping = shape != null;
         mRoundedClipPath.reset();
         if (shape != null) {
@@ -5929,17 +5936,36 @@
             mRoundedRectClippingTop = (int) bounds.getTop();
             mRoundedRectClippingRight = (int) bounds.getRight();
             mRoundedRectClippingBottom = (int) bounds.getBottom();
-            mBgCornerRadii[0] = shape.getTopRadius();
-            mBgCornerRadii[1] = shape.getTopRadius();
-            mBgCornerRadii[2] = shape.getTopRadius();
-            mBgCornerRadii[3] = shape.getTopRadius();
-            mBgCornerRadii[4] = shape.getBottomRadius();
-            mBgCornerRadii[5] = shape.getBottomRadius();
-            mBgCornerRadii[6] = shape.getBottomRadius();
-            mBgCornerRadii[7] = shape.getBottomRadius();
+            mRoundedClipCornerRadii[0] = shape.getTopRadius();
+            mRoundedClipCornerRadii[1] = shape.getTopRadius();
+            mRoundedClipCornerRadii[2] = shape.getTopRadius();
+            mRoundedClipCornerRadii[3] = shape.getTopRadius();
+            mRoundedClipCornerRadii[4] = shape.getBottomRadius();
+            mRoundedClipCornerRadii[5] = shape.getBottomRadius();
+            mRoundedClipCornerRadii[6] = shape.getBottomRadius();
+            mRoundedClipCornerRadii[7] = shape.getBottomRadius();
             mRoundedClipPath.addRoundRect(
                     bounds.getLeft(), bounds.getTop(), bounds.getRight(), bounds.getBottom(),
-                    mBgCornerRadii, Path.Direction.CW);
+                    mRoundedClipCornerRadii, Path.Direction.CW);
+        }
+        invalidate();
+    }
+
+    @Override
+    public void setNegativeClippingShape(@Nullable ShadeScrimShape shape) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+        if (Objects.equals(mScrollViewFields.getNegativeClippingShape(), shape)) return;
+
+        mScrollViewFields.setNegativeClippingShape(shape);
+        mShouldUseNegativeRoundedRectClipping = shape != null;
+        mNegativeRoundedClipPath.reset();
+        if (shape != null) {
+            ShadeScrimBounds bounds = shape.getBounds();
+            float bottomRadius = shape.getBottomRadius();
+            mNegativeRoundedClipPath.addRoundRect(
+                    bounds.getLeft(), bounds.getTop(), bounds.getRight(), bounds.getBottom(),
+                    new float[]{0, 0, 0, 0, bottomRadius, bottomRadius, bottomRadius, bottomRadius},
+                    Path.Direction.CW);
         }
         invalidate();
     }
@@ -5952,21 +5978,22 @@
         SceneContainerFlag.assertInLegacyMode();
         if (mRoundedRectClippingLeft == left && mRoundedRectClippingRight == right
                 && mRoundedRectClippingBottom == bottom && mRoundedRectClippingTop == top
-                && mBgCornerRadii[0] == topRadius && mBgCornerRadii[5] == bottomRadius) {
+                && mRoundedClipCornerRadii[0] == topRadius
+                && mRoundedClipCornerRadii[5] == bottomRadius) {
             return;
         }
         mRoundedRectClippingLeft = left;
         mRoundedRectClippingTop = top;
         mRoundedRectClippingBottom = bottom;
         mRoundedRectClippingRight = right;
-        mBgCornerRadii[0] = topRadius;
-        mBgCornerRadii[1] = topRadius;
-        mBgCornerRadii[2] = topRadius;
-        mBgCornerRadii[3] = topRadius;
-        mBgCornerRadii[4] = bottomRadius;
-        mBgCornerRadii[5] = bottomRadius;
-        mBgCornerRadii[6] = bottomRadius;
-        mBgCornerRadii[7] = bottomRadius;
+        mRoundedClipCornerRadii[0] = topRadius;
+        mRoundedClipCornerRadii[1] = topRadius;
+        mRoundedClipCornerRadii[2] = topRadius;
+        mRoundedClipCornerRadii[3] = topRadius;
+        mRoundedClipCornerRadii[4] = bottomRadius;
+        mRoundedClipCornerRadii[5] = bottomRadius;
+        mRoundedClipCornerRadii[6] = bottomRadius;
+        mRoundedClipCornerRadii[7] = bottomRadius;
         updateRoundedClipPath();
     }
 
@@ -5988,7 +6015,7 @@
                 mRoundedRectClippingTop + mRoundedRectClippingYTranslation,
                 mRoundedRectClippingRight,
                 mRoundedRectClippingBottom + mRoundedRectClippingYTranslation,
-                mBgCornerRadii, Path.Direction.CW);
+                mRoundedClipCornerRadii, Path.Direction.CW);
         if (mShouldUseRoundedRectClipping) {
             invalidate();
         }
@@ -6112,35 +6139,49 @@
     }
 
     @Override
-    protected void dispatchDraw(Canvas canvas) {
-        if (mShouldUseRoundedRectClipping && !mLaunchingNotification) {
+    protected void dispatchDraw(@NonNull Canvas canvas) {
+        if (!mLaunchingNotification) {
             // When launching notifications, we're clipping the children individually instead of in
             // dispatchDraw
-            // Let's clip rounded.
-            canvas.clipPath(mRoundedClipPath);
+            if (mShouldUseRoundedRectClipping) {
+                // Let's clip rounded.
+                canvas.clipPath(mRoundedClipPath);
+            }
+            if (mShouldUseNegativeRoundedRectClipping) {
+                // subtract the negative path if it is defined
+                canvas.clipOutPath(mNegativeRoundedClipPath);
+            }
         }
         super.dispatchDraw(canvas);
     }
 
     @Override
     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
-        if (mShouldUseRoundedRectClipping && mLaunchingNotification) {
+        boolean shouldUseClipping =
+                mShouldUseRoundedRectClipping || mShouldUseNegativeRoundedRectClipping;
+        if (mLaunchingNotification && shouldUseClipping) {
             // Let's clip children individually during notification launch
             canvas.save();
             ExpandableView expandableView = (ExpandableView) child;
             Path clipPath;
+            Path clipOutPath;
             if (expandableView.isExpandAnimationRunning()
                     || ((ExpandableView) child).hasExpandingChild()) {
                 // When launching the notification, it is not clipped by this layout, but by the
                 // view itself. This is because the view is Translating in Z, where this clipPath
                 // wouldn't apply.
                 clipPath = null;
+                clipOutPath = null;
             } else {
                 clipPath = mRoundedClipPath;
+                clipOutPath = mNegativeRoundedClipPath;
             }
-            if (clipPath != null) {
+            if (mShouldUseRoundedRectClipping && clipPath != null) {
                 canvas.clipPath(clipPath);
             }
+            if (mShouldUseNegativeRoundedRectClipping && clipOutPath != null) {
+                canvas.clipOutPath(clipOutPath);
+            }
             boolean result = super.drawChild(canvas, child, drawingTime);
             canvas.restore();
             return result;
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 c717e3b..dc0fae8 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
@@ -1207,7 +1207,11 @@
         return mView.getEmptyShadeViewHeight();
     }
 
-    /** Set the max alpha for keyguard */
+    /**
+     * Controls fading out Notifications during animations over the LockScreen, such opening or
+     * closing the shade. Note that we don't restrict Notification alpha in certain cases,
+     * like when the Shade is opened from a HUN.
+     */
     public void setMaxAlphaForKeyguard(float alpha, String source) {
         mMaxAlphaForKeyguard = alpha;
         mMaxAlphaForKeyguardSource = source;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt
index fa20e43..2593ef0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt
@@ -33,7 +33,10 @@
  */
 class ScrollViewFields {
     /** Used to produce the clipping path */
-    var scrimClippingShape: ShadeScrimShape? = null
+    var clippingShape: ShadeScrimShape? = null
+
+    /** Used to produce a negative clipping path */
+    var negativeClippingShape: ShadeScrimShape? = null
 
     /** Scroll state of the notification shade. */
     var scrollState: ShadeScrollState = ShadeScrollState()
@@ -97,7 +100,8 @@
 
     fun dump(pw: IndentingPrintWriter) {
         pw.printSection("StackViewStates") {
-            pw.println("scrimClippingShape", scrimClippingShape)
+            pw.println("scrimClippingShape", clippingShape)
+            pw.println("negativeClippingShape", negativeClippingShape)
             pw.println("scrollState", scrollState)
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt
index 5ec4c89..1d196c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.notification.stack.shared.model.AccessibilityScrollEvent
 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
+import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrollState
 import java.util.function.Consumer
 import javax.inject.Inject
@@ -42,7 +43,15 @@
      *
      * When `null`, clipping should not be applied to notifications.
      */
-    val shadeScrimBounds = MutableStateFlow<ShadeScrimBounds?>(null)
+    val notificationShadeScrimBounds = MutableStateFlow<ShadeScrimBounds?>(null)
+
+    /**
+     * The shape of the QuickSettings overlay panel. Used to clip Notification content when the QS
+     * covers it.
+     *
+     * When `null`, it doesn't affect notification clipping.
+     */
+    val qsPanelShape = MutableStateFlow<ShadeScrimShape?>(null)
 
     /** height made available to the notifications in the size-constrained mode of lock screen. */
     val constrainedAvailableSpace = MutableStateFlow(0)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
index d4dd1d4b..406a0db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.statusbar.notification.stack.shared.model.AccessibilityScrollEvent
 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding
+import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrollState
 import java.util.function.Consumer
 import javax.inject.Inject
@@ -47,8 +48,11 @@
     shadeInteractor: ShadeInteractor,
 ) {
     /** The bounds of the notification stack in the current scene. */
-    val shadeScrimBounds: StateFlow<ShadeScrimBounds?> =
-        placeholderRepository.shadeScrimBounds.asStateFlow()
+    val notificationShadeScrimBounds: StateFlow<ShadeScrimBounds?> =
+        placeholderRepository.notificationShadeScrimBounds.asStateFlow()
+
+    /** The shape of the QuickSettingsShadeOverlay panel */
+    val qsPanelShape: StateFlow<ShadeScrimShape?> = placeholderRepository.qsPanelShape.asStateFlow()
 
     /**
      * Whether the stack is expanding from GONE-with-HUN to SHADE
@@ -119,9 +123,15 @@
     }
 
     /** Sets the position of the notification stack in the current scene. */
-    fun setShadeScrimBounds(bounds: ShadeScrimBounds?) {
-        check(bounds == null || bounds.top <= bounds.bottom) { "Invalid bounds: $bounds" }
-        placeholderRepository.shadeScrimBounds.value = bounds
+    fun setNotificationShadeScrimBounds(bounds: ShadeScrimBounds?) {
+        checkValidBounds(bounds)
+        placeholderRepository.notificationShadeScrimBounds.value = bounds
+    }
+
+    /** Sets the bounds of the QuickSettings overlay panel */
+    fun setQsPanelShape(shape: ShadeScrimShape?) {
+        checkValidBounds(shape?.bounds)
+        placeholderRepository.qsPanelShape.value = shape
     }
 
     /** Updates the current scroll state of the notification shade. */
@@ -156,4 +166,8 @@
     fun setConstrainedAvailableSpace(height: Int) {
         placeholderRepository.constrainedAvailableSpace.value = height
     }
+
+    private fun checkValidBounds(bounds: ShadeScrimBounds?) {
+        check(bounds == null || bounds.top <= bounds.bottom) { "Invalid bounds: $bounds" }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
index d302fb6..5fec096 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
@@ -49,8 +49,14 @@
     /** Max alpha for this view */
     fun setMaxAlpha(alpha: Float)
 
-    /** Set the clipping bounds used when drawing */
-    fun setScrimClippingShape(shape: ShadeScrimShape?)
+    /** Sets a clipping shape, which defines the drawable area of this view. */
+    fun setClippingShape(shape: ShadeScrimShape?)
+
+    /**
+     * Sets a clipping shape, which defines the non-drawable area of this view. The final drawing
+     * area is the difference of the clipping shape, and the negative clipping shape.
+     */
+    fun setNegativeClippingShape(shape: ShadeScrimShape?)
 
     /** set the y position in px of the top of the stack in this view's coordinates */
     fun setStackTop(stackTop: Float)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
index 1d7e658..6385d53 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -99,6 +99,9 @@
                 .inflate(R.layout.status_bar_notification_shelf, view, false) as NotificationShelf
         view.setShelf(shelf)
 
+        // Create viewModels once, and only when needed.
+        val footerViewModel by lazy { viewModel.footerViewModelFactory.create() }
+        val emptyShadeViewModel by lazy { viewModel.emptyShadeViewModelFactory.create() }
         view.repeatWhenAttached {
             lifecycleScope.launch {
                 if (SceneContainerFlag.isEnabled) {
@@ -109,12 +112,18 @@
 
                 val hasNonClearableSilentNotifications: StateFlow<Boolean> =
                     viewModel.hasNonClearableSilentNotifications.stateIn(this)
-                launch { reinflateAndBindFooter(view, hasNonClearableSilentNotifications) }
+                launch {
+                    reinflateAndBindFooter(
+                        footerViewModel,
+                        view,
+                        hasNonClearableSilentNotifications,
+                    )
+                }
                 launch {
                     if (ModesEmptyShadeFix.isEnabled) {
-                        reinflateAndBindEmptyShade(view)
+                        reinflateAndBindEmptyShade(emptyShadeViewModel, view)
                     } else {
-                        bindEmptyShadeLegacy(viewModel.emptyShadeViewFactory.create(), view)
+                        bindEmptyShadeLegacy(emptyShadeViewModel, view)
                     }
                 }
                 launch { bindSilentHeaderClickListener(view, hasNonClearableSilentNotifications) }
@@ -134,31 +143,30 @@
     }
 
     private suspend fun reinflateAndBindFooter(
+        footerViewModel: FooterViewModel,
         parentView: NotificationStackScrollLayout,
         hasNonClearableSilentNotifications: StateFlow<Boolean>,
     ) {
-        viewModel.footer.getOrNull()?.let { footerViewModel ->
-            // The footer needs to be re-inflated every time the theme or the font size changes.
-            configuration
-                .inflateLayout<FooterView>(
-                    if (NotifRedesignFooter.isEnabled) R.layout.notification_2025_footer
-                    else R.layout.status_bar_notification_footer,
-                    parentView,
-                    attachToRoot = false,
-                )
-                .flowOn(backgroundDispatcher)
-                .collectLatest { footerView: FooterView ->
-                    traceAsync("bind FooterView") {
-                        parentView.setFooterView(footerView)
-                        bindFooter(
-                            footerView,
-                            footerViewModel,
-                            parentView,
-                            hasNonClearableSilentNotifications,
-                        )
-                    }
+        // The footer needs to be re-inflated every time the theme or the font size changes.
+        configuration
+            .inflateLayout<FooterView>(
+                if (NotifRedesignFooter.isEnabled) R.layout.notification_2025_footer
+                else R.layout.status_bar_notification_footer,
+                parentView,
+                attachToRoot = false,
+            )
+            .flowOn(backgroundDispatcher)
+            .collectLatest { footerView: FooterView ->
+                traceAsync("bind FooterView") {
+                    parentView.setFooterView(footerView)
+                    bindFooter(
+                        footerView,
+                        footerViewModel,
+                        parentView,
+                        hasNonClearableSilentNotifications,
+                    )
                 }
-        }
+            }
     }
 
     /**
@@ -219,7 +227,10 @@
         notificationActivityStarter.get().startHistoryIntent(view, /* showHistory= */ true)
     }
 
-    private suspend fun reinflateAndBindEmptyShade(parentView: NotificationStackScrollLayout) {
+    private suspend fun reinflateAndBindEmptyShade(
+        emptyShadeViewModel: EmptyShadeViewModel,
+        parentView: NotificationStackScrollLayout,
+    ) {
         ModesEmptyShadeFix.assertInNewMode()
         // The empty shade needs to be re-inflated every time the theme or the font size
         // changes.
@@ -233,7 +244,7 @@
             .collectLatest { emptyShadeView: EmptyShadeView ->
                 traceAsync("bind EmptyShadeView") {
                     parentView.setEmptyShadeView(emptyShadeView)
-                    bindEmptyShade(emptyShadeView, viewModel.emptyShadeViewFactory.create())
+                    bindEmptyShade(emptyShadeView, emptyShadeViewModel)
                 }
             }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
index ef68b4d..8709d27 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
@@ -79,8 +79,17 @@
 
             launch {
                 viewModel
-                    .shadeScrimShape(cornerRadius = scrimRadius, viewLeftOffset = viewLeftOffset)
-                    .collectTraced { view.setScrimClippingShape(it) }
+                    .notificationScrimShape(
+                        cornerRadius = scrimRadius,
+                        viewLeftOffset = viewLeftOffset,
+                    )
+                    .collectTraced { view.setClippingShape(it) }
+            }
+
+            launch {
+                viewModel.qsScrimShape(viewLeftOffset = viewLeftOffset).collectTraced {
+                    view.setNegativeClippingShape(it)
+                }
             }
 
             launch { viewModel.maxAlpha.collectTraced { view.setMaxAlpha(it) } }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
index fcc671a..5ed1889 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -56,8 +56,8 @@
 constructor(
     val shelf: NotificationShelfViewModel,
     val hideListViewModel: HideListViewModel,
-    val footer: Optional<FooterViewModel>,
-    val emptyShadeViewFactory: EmptyShadeViewModel.Factory,
+    val footerViewModelFactory: FooterViewModel.Factory,
+    val emptyShadeViewModelFactory: EmptyShadeViewModel.Factory,
     val logger: Optional<NotificationLoggerViewModel>,
     activeNotificationsInteractor: ActiveNotificationsInteractor,
     notificationStackInteractor: NotificationStackInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index 1bb205c..80ebf43 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -59,7 +59,7 @@
 @AssistedInject
 constructor(
     dumpManager: DumpManager,
-    stackAppearanceInteractor: NotificationStackAppearanceInteractor,
+    private val stackAppearanceInteractor: NotificationStackAppearanceInteractor,
     shadeInteractor: ShadeInteractor,
     private val remoteInputInteractor: RemoteInputInteractor,
     private val sceneInteractor: SceneInteractor,
@@ -221,7 +221,7 @@
     private val shadeScrimClipping: Flow<ShadeScrimClipping?> =
         combine(
                 qsAllowsClipping,
-                stackAppearanceInteractor.shadeScrimBounds,
+                stackAppearanceInteractor.notificationShadeScrimBounds,
                 stackAppearanceInteractor.shadeScrimRounding,
             ) { qsAllowsClipping, bounds, rounding ->
                 bounds?.takeIf { qsAllowsClipping }?.let { ShadeScrimClipping(it, rounding) }
@@ -229,7 +229,7 @@
             .distinctUntilChanged()
             .dumpWhileCollecting("stackClipping")
 
-    fun shadeScrimShape(
+    fun notificationScrimShape(
         cornerRadius: Flow<Int>,
         viewLeftOffset: Flow<Int>,
     ): Flow<ShadeScrimShape?> =
@@ -243,6 +243,12 @@
             }
             .dumpWhileCollecting("shadeScrimShape")
 
+    fun qsScrimShape(viewLeftOffset: Flow<Int>): Flow<ShadeScrimShape?> =
+        combine(stackAppearanceInteractor.qsPanelShape, viewLeftOffset) { shape, leftOffset ->
+                shape?.let { it.copy(bounds = it.bounds.minus(leftOffset = leftOffset)) }
+            }
+            .dumpWhileCollecting("qsScrimShape")
+
     /**
      * Max alpha to apply directly to the view based on the compose placeholder.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index 49cd7cb..5d55022 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlagsClassic
@@ -40,7 +41,6 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /**
  * ViewModel used by the Notification placeholders inside the scene container to update the
@@ -89,7 +89,7 @@
 
     /** Notifies that the bounds of the notification scrim have changed. */
     fun onScrimBoundsChanged(bounds: ShadeScrimBounds?) {
-        interactor.setShadeScrimBounds(bounds)
+        interactor.setNotificationShadeScrimBounds(bounds)
     }
 
     /** Sets the available space */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index aa13089..3f44f7b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -234,7 +234,7 @@
             )
         }
         if (ShadeWindowGoesAround.isEnabled && event.action == MotionEvent.ACTION_DOWN) {
-            lazyStatusBarShadeDisplayPolicy.get().onStatusBarTouched(context.displayId)
+            lazyStatusBarShadeDisplayPolicy.get().onStatusBarTouched(event, mView.width)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt
index 30c529a..3e7094a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt
@@ -34,6 +34,9 @@
      */
     suspend fun startObservingCarrierConfigUpdates()
 
-    /** Gets a cached [SystemUiCarrierConfig], or creates a new one which will track the defaults */
-    fun getOrCreateConfigForSubId(subId: Int): SystemUiCarrierConfig
+    /**
+     * Gets a cached [SystemUiCarrierConfig], or creates a new one which will track the defaults. A
+     * null [maybeSubId] will return the default carrier config.
+     */
+    fun getOrCreateConfigForSubId(maybeSubId: Int?): SystemUiCarrierConfig
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryImpl.kt
index 9a97f19..b320379 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryImpl.kt
@@ -20,6 +20,7 @@
 import android.os.PersistableBundle
 import android.telephony.CarrierConfigManager
 import android.telephony.SubscriptionManager
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
 import android.util.SparseArray
 import androidx.annotation.VisibleForTesting
 import androidx.core.util.getOrElse
@@ -51,7 +52,7 @@
     private val defaultConfig: PersistableBundle by lazy { CarrierConfigManager.getDefaultConfig() }
     // Used for logging the default config in the dumpsys
     private val defaultConfigForLogs: SystemUiCarrierConfig by lazy {
-        SystemUiCarrierConfig(-1, defaultConfig)
+        SystemUiCarrierConfig(INVALID_SUBSCRIPTION_ID, defaultConfig)
     }
 
     private val configs = SparseArray<SystemUiCarrierConfig>()
@@ -89,7 +90,10 @@
         configToUpdate.processNewCarrierConfig(config)
     }
 
-    override fun getOrCreateConfigForSubId(subId: Int): SystemUiCarrierConfig {
+    override fun getOrCreateConfigForSubId(maybeSubId: Int?): SystemUiCarrierConfig {
+        // See CarrierConfigManager#getConfigForSubId(), passing INVALID_SUBSCRIPTION_ID yields
+        // the default carrier config
+        val subId = maybeSubId ?: INVALID_SUBSCRIPTION_ID
         return configs.getOrElse(subId) {
             val config = SystemUiCarrierConfig(subId, defaultConfig)
             val carrierConfig = carrierConfigManager?.getConfigForSubId(subId)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
index 32e9c85..09a6269 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
@@ -48,8 +48,8 @@
      */
     val activeSubChangedInGroupEvent: Flow<Unit>
 
-    /** Tracks [SubscriptionManager.getDefaultDataSubscriptionId] */
-    val defaultDataSubId: StateFlow<Int>
+    /** Tracks [SubscriptionManager.getDefaultDataSubscriptionId]. Null if there is no default */
+    val defaultDataSubId: StateFlow<Int?>
 
     /**
      * True if the default network connection is a mobile-like connection and false otherwise.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
index fc76691..252ebe6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
@@ -164,7 +164,7 @@
 
     override fun getIsAnySimSecure(): Boolean = activeRepo.value.getIsAnySimSecure()
 
-    override val defaultDataSubId: StateFlow<Int> =
+    override val defaultDataSubId: StateFlow<Int?> =
         activeRepo
             .flatMapLatest { it.defaultDataSubId }
             .stateIn(scope, SharingStarted.WhileSubscribed(), realRepository.defaultDataSubId.value)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
index 936954f..b608e53 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
@@ -166,7 +166,7 @@
     private fun <K, V> Map<K, V>.reverse() = entries.associateBy({ it.value }) { it.key }
 
     // TODO(b/261029387): add a command for this value
-    override val defaultDataSubId = MutableStateFlow(INVALID_SUBSCRIPTION_ID)
+    override val defaultDataSubId: MutableStateFlow<Int?> = MutableStateFlow(null)
 
     // TODO(b/261029387): not yet supported
     override val mobileIsDefault: StateFlow<Boolean> = MutableStateFlow(true)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index b762751..aa6da61 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -249,7 +249,7 @@
                 tableLogger,
                 LOGGING_PREFIX,
                 columnName = "activeSubId",
-                initialValue = INVALID_SUBSCRIPTION_ID,
+                initialValue = null,
             )
             .stateIn(scope, started = SharingStarted.WhileSubscribed(), null)
 
@@ -264,22 +264,31 @@
             }
             .stateIn(scope, SharingStarted.WhileSubscribed(), null)
 
-    override val defaultDataSubId: StateFlow<Int> =
+    override val defaultDataSubId: StateFlow<Int?> =
         broadcastDispatcher
             .broadcastFlow(
                 IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
             ) { intent, _ ->
-                intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, INVALID_SUBSCRIPTION_ID)
+                val subId =
+                    intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, INVALID_SUBSCRIPTION_ID)
+                if (subId == INVALID_SUBSCRIPTION_ID) {
+                    null
+                } else {
+                    subId
+                }
             }
             .distinctUntilChanged()
             .logDiffsForTable(
                 tableLogger,
                 LOGGING_PREFIX,
                 columnName = "defaultSubId",
-                initialValue = INVALID_SUBSCRIPTION_ID,
+                initialValue = null,
             )
-            .onStart { emit(subscriptionManagerProxy.getDefaultDataSubscriptionId()) }
-            .stateIn(scope, SharingStarted.WhileSubscribed(), INVALID_SUBSCRIPTION_ID)
+            .onStart {
+                val subId = subscriptionManagerProxy.getDefaultDataSubscriptionId()
+                emit(if (subId == INVALID_SUBSCRIPTION_ID) null else subId)
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), null)
 
     private val carrierConfigChangedEvent =
         broadcastDispatcher
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index 78731fa..be56461 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -72,7 +72,7 @@
     val filteredSubscriptions: Flow<List<SubscriptionModel>>
 
     /** Subscription ID of the current default data subscription */
-    val defaultDataSubId: Flow<Int>
+    val defaultDataSubId: Flow<Int?>
 
     /**
      * The current list of [MobileIconInteractor]s associated with the current list of
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
index 71e1918..b1cc208 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
@@ -32,6 +32,7 @@
 import androidx.compose.ui.platform.ComposeView
 import androidx.compose.ui.platform.ViewCompositionStrategy
 import androidx.compose.ui.viewinterop.AndroidView
+import androidx.core.view.isVisible
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.compose.theme.PlatformTheme
@@ -39,6 +40,7 @@
 import com.android.systemui.plugins.DarkIconDispatcher
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.chips.ui.compose.OngoingActivityChips
+import com.android.systemui.statusbar.core.StatusBarRootModernization
 import com.android.systemui.statusbar.data.repository.DarkIconDispatcherStore
 import com.android.systemui.statusbar.events.domain.interactor.SystemStatusEventAnimationInteractor
 import com.android.systemui.statusbar.featurepods.popups.StatusBarPopupChips
@@ -142,10 +144,14 @@
 
     Box(Modifier.fillMaxSize()) {
         // TODO(b/364360986): remove this before rolling the flag forward
-        Disambiguation(viewModel = statusBarViewModel)
+        if (StatusBarRootModernization.SHOW_DISAMBIGUATION) {
+            Disambiguation(viewModel = statusBarViewModel)
+        }
 
         Row(Modifier.fillMaxSize()) {
             val scope = rememberCoroutineScope()
+            val visible =
+                statusBarViewModel.shouldHomeStatusBarBeVisible.collectAsStateWithLifecycle(false)
             AndroidView(
                 factory = { context ->
                     val inflater = LayoutInflater.from(context)
@@ -280,7 +286,12 @@
                     }
                     onViewCreated(phoneStatusBarView)
                     phoneStatusBarView
-                }
+                },
+                update = { view ->
+                    // Show or hide the entire status bar. This is important so that we aren't
+                    // visible when first inflated
+                    view.isVisible = visible.value
+                },
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
index d9d9a29..6ff4354 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/HomeStatusBarViewModel.kt
@@ -80,6 +80,9 @@
  * so that it's all in one place and easily testable outside of the fragment.
  */
 interface HomeStatusBarViewModel {
+    /** Should the entire status bar be hidden? */
+    val shouldHomeStatusBarBeVisible: Flow<Boolean>
+
     /**
      * True if the device is currently transitioning from lockscreen to occluded and false
      * otherwise.
@@ -271,10 +274,12 @@
             isHomeScreenStatusBarAllowedLegacy
         }
 
-    private val shouldHomeStatusBarBeVisible =
-        combine(isHomeStatusBarAllowed, keyguardInteractor.isSecureCameraActive) {
+    override val shouldHomeStatusBarBeVisible =
+        combine(
             isHomeStatusBarAllowed,
-            isSecureCameraActive ->
+            keyguardInteractor.isSecureCameraActive,
+            headsUpNotificationInteractor.statusBarHeadsUpStatus,
+        ) { isHomeStatusBarAllowed, isSecureCameraActive, headsUpState ->
             // When launching the camera over the lockscreen, the status icons would typically
             // become visible momentarily before animating out, since we're not yet aware that the
             // launching camera activity is fullscreen. Even once the activity finishes launching,
@@ -282,7 +287,7 @@
             // tells us to hide them.
             // To ensure that this high-visibility animation is smooth, keep the icons hidden during
             // a camera launch. See b/257292822.
-            isHomeStatusBarAllowed && !isSecureCameraActive
+            headsUpState.isPinned || (isHomeStatusBarAllowed && !isSecureCameraActive)
         }
 
     private val isAnyChipVisible =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/StatusBarOperatorNameViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/StatusBarOperatorNameViewModel.kt
index 7ae74c3..0b83c4e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/StatusBarOperatorNameViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/StatusBarOperatorNameViewModel.kt
@@ -22,6 +22,7 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
 
 /**
  * View model for the operator name (aka carrier name) of the carrier for the default data
@@ -34,6 +35,10 @@
 constructor(mobileIconsInteractor: MobileIconsInteractor) {
     val operatorName: Flow<String?> =
         mobileIconsInteractor.defaultDataSubId.flatMapLatest {
-            mobileIconsInteractor.getMobileConnectionInteractorForSubId(it).carrierName
+            if (it == null) {
+                flowOf(null)
+            } else {
+                mobileIconsInteractor.getMobileConnectionInteractorForSubId(it).carrierName
+            }
         }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
index fdc2d8d..5fa15b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
@@ -43,6 +43,7 @@
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
@@ -123,12 +124,20 @@
      * explicitly wants a shortcut to DND). Please prefer using [modes] or [activeModes] in all
      * other scenarios.
      */
-    val dndMode: StateFlow<ZenMode?> by lazy {
-        ModesUi.assertInNewMode()
-        zenModeRepository.modes
-            .map { modes -> modes.singleOrNull { it.isManualDnd } }
-            .stateIn(scope = backgroundScope, started = SharingStarted.Eagerly, initialValue = null)
-    }
+    val dndMode: StateFlow<ZenMode?> =
+        if (ModesUi.isEnabled)
+            zenModeRepository.modes
+                .map { modes -> modes.singleOrNull { it.isManualDnd } }
+                .stateIn(
+                    scope = backgroundScope,
+                    started = SharingStarted.Eagerly,
+                    initialValue = null,
+                )
+        else MutableStateFlow<ZenMode?>(null)
+        get() {
+            ModesUi.assertInNewMode()
+            return field
+        }
 
     /** Flow returning the currently active mode(s), if any. */
     val activeModes: Flow<ActiveZenModes> =
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt
index 284e23e..47c82e3 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt
@@ -26,7 +26,10 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.input.pointer.pointerInteropFilter
@@ -48,8 +51,13 @@
     onBack: () -> Unit,
 ) {
     BackHandler(onBack = onBack)
+    var cachedTutorialState: TutorialActionState by
+        rememberSaveable(stateSaver = TutorialActionState.stateSaver()) {
+            mutableStateOf(NotStarted)
+        }
     val easterEggTriggered by easterEggTriggeredFlow.collectAsStateWithLifecycle(false)
-    val tutorialState by tutorialStateFlow.collectAsStateWithLifecycle(NotStarted)
+    val tutorialState by tutorialStateFlow.collectAsStateWithLifecycle(cachedTutorialState)
+    cachedTutorialState = tutorialState
     TouchpadGesturesHandlingBox(
         motionEventConsumer,
         tutorialState,
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
index 66a900b..c8a5840 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt
@@ -218,9 +218,11 @@
             verticalArrangement = Arrangement.Center,
             horizontalAlignment = Alignment.CenterHorizontally,
         ) {
+            // contentDescription is set to null because the icon is decorative and we don't want to
+            // repeat the text twice
             Icon(
                 imageVector = icon,
-                contentDescription = text,
+                contentDescription = null,
                 modifier = Modifier.width(30.dp).height(30.dp),
                 tint = iconColor,
             )
diff --git a/packages/SystemUI/src/com/android/systemui/util/DelayableMarqueeTextView.kt b/packages/SystemUI/src/com/android/systemui/util/DelayableMarqueeTextView.kt
index ef9340a3..ffaddfb 100644
--- a/packages/SystemUI/src/com/android/systemui/util/DelayableMarqueeTextView.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/DelayableMarqueeTextView.kt
@@ -20,11 +20,13 @@
 import android.util.AttributeSet
 import com.android.systemui.res.R
 
-class DelayableMarqueeTextView @JvmOverloads constructor(
+open class DelayableMarqueeTextView
+@JvmOverloads
+constructor(
     context: Context,
     attrs: AttributeSet? = null,
     defStyleAttr: Int = 0,
-    defStyleRes: Int = 0
+    defStyleRes: Int = 0,
 ) : SafeMarqueeTextView(context, attrs, defStyleAttr, defStyleRes) {
 
     var marqueeDelay: Long = DEFAULT_MARQUEE_DELAY
@@ -39,16 +41,20 @@
     }
 
     init {
-        val typedArray = context.theme.obtainStyledAttributes(
+        val typedArray =
+            context.theme.obtainStyledAttributes(
                 attrs,
                 R.styleable.DelayableMarqueeTextView,
                 defStyleAttr,
-                defStyleRes
-        )
-        marqueeDelay = typedArray.getInteger(
-                R.styleable.DelayableMarqueeTextView_marqueeDelay,
-                DEFAULT_MARQUEE_DELAY.toInt()
-        ).toLong()
+                defStyleRes,
+            )
+        marqueeDelay =
+            typedArray
+                .getInteger(
+                    R.styleable.DelayableMarqueeTextView_marqueeDelay,
+                    DEFAULT_MARQUEE_DELAY.toInt(),
+                )
+                .toLong()
         typedArray.recycle()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
index 96630ca..908249d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
@@ -144,7 +144,6 @@
                                     ringerState.orientation,
                                 )
                             }
-
                             is RingerDrawerState.Closed -> {
                                 if (
                                     uiModel.selectedButton.ringerMode ==
@@ -189,7 +188,6 @@
                                     }
                                 }
                             }
-
                             is RingerDrawerState.Open -> {
                                 drawerContainer.animateAndBindDrawerButtons(
                                     viewModel,
@@ -220,7 +218,6 @@
                             }
                         }
                     }
-
                     is RingerViewModelState.Unavailable -> {
                         drawerContainer.visibility = View.GONE
                         volumeDialogBackgroundView.setBackgroundResource(
@@ -251,7 +248,7 @@
                     .requireViewById<ImageButton>(R.id.volume_drawer_button)
             val previousIndex =
                 uiModel.availableButtons.indexOfFirst {
-                    it?.ringerMode == uiModel.drawerState.previousMode
+                    it.ringerMode == uiModel.drawerState.previousMode
                 }
             val unselectedButton =
                 getChildAt(count - previousIndex)
@@ -306,20 +303,18 @@
     ) {
         val count = uiModel.availableButtons.size
         uiModel.availableButtons.fastForEachIndexed { index, ringerButton ->
-            ringerButton?.let {
-                val view = getChildAt(count - index)
-                val isOpen = uiModel.drawerState is RingerDrawerState.Open
-                if (index == uiModel.currentButtonIndex) {
-                    view.bindDrawerButton(
-                        if (isOpen) it else uiModel.selectedButton,
-                        viewModel,
-                        isOpen,
-                        isSelected = true,
-                        isAnimated = isAnimated,
-                    )
-                } else {
-                    view.bindDrawerButton(it, viewModel, isOpen, isAnimated = isAnimated)
-                }
+            val view = getChildAt(count - index)
+            val isOpen = uiModel.drawerState is RingerDrawerState.Open
+            if (index == uiModel.currentButtonIndex) {
+                view.bindDrawerButton(
+                    if (isOpen) ringerButton else uiModel.selectedButton,
+                    viewModel,
+                    isOpen,
+                    isSelected = true,
+                    isAnimated = isAnimated,
+                )
+            } else {
+                view.bindDrawerButton(ringerButton, viewModel, isOpen, isAnimated = isAnimated)
             }
         }
         onAnimationEnd?.run()
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerViewModel.kt
index 96d4f62..8613610 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerViewModel.kt
@@ -19,7 +19,7 @@
 /** Models volume dialog ringer */
 data class RingerViewModel(
     /** List of the available buttons according to the available modes */
-    val availableButtons: List<RingerButtonViewModel?>,
+    val availableButtons: List<RingerButtonViewModel>,
     /** The index of the currently selected button */
     val currentButtonIndex: Int,
     /** Currently selected button. */
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt
index eec64d9..b0d6d622 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt
@@ -35,6 +35,7 @@
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.onConfigChanged
+import com.android.systemui.util.time.SystemClock
 import com.android.systemui.volume.Events
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
@@ -50,10 +51,13 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 
+private const val DRAWER_STATE_ANIMATION_DURATION = 400L
 private const val SHOW_RINGER_TOAST_COUNT = 12
 
 @VolumeDialogScope
@@ -69,6 +73,7 @@
     private val volumeDialogLogger: VolumeDialogLogger,
     private val visibilityInteractor: VolumeDialogVisibilityInteractor,
     configurationController: ConfigurationController,
+    private val systemClock: SystemClock,
 ) {
 
     private val drawerState = MutableStateFlow<RingerDrawerState>(RingerDrawerState.Initial)
@@ -106,9 +111,29 @@
             .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
             .build()
 
+    private var lastClickTime = 0L
+    init {
+        ringerViewModel
+            .onEach { viewModelState ->
+                when (viewModelState) {
+                    is RingerViewModelState.Available ->
+                        volumeDialogLogger.onRingerDrawerAvailable(
+                            viewModelState.uiModel.availableButtons.map { it.ringerMode }
+                        )
+                    is RingerViewModelState.Unavailable ->
+                        volumeDialogLogger.onRingerDrawerUnavailable()
+                }
+            }
+            .launchIn(coroutineScope)
+    }
+
     fun onRingerButtonClicked(ringerMode: RingerMode, isSelectedButton: Boolean = false) {
+        val currentTime = systemClock.currentTimeMillis()
+        if (currentTime - lastClickTime < DRAWER_STATE_ANIMATION_DURATION) return
+        lastClickTime = currentTime
         if (drawerState.value is RingerDrawerState.Open && !isSelectedButton) {
             Events.writeEvent(Events.EVENT_RINGER_TOGGLE, ringerMode.value)
+            volumeDialogLogger.onRingerModeChanged(ringerMode)
             provideTouchFeedback(ringerMode)
             maybeShowToast(ringerMode)
             ringerInteractor.setRingerMode(ringerMode)
@@ -159,7 +184,9 @@
                 RingerViewModelState.Available(
                     RingerViewModel(
                         availableButtons =
-                            availableModes.map { mode -> toButtonViewModel(mode, isZenMuted) },
+                            availableModes.mapNotNull { mode ->
+                                toButtonViewModel(mode, isZenMuted)
+                            },
                         currentButtonIndex = currentIndex,
                         selectedButton = it,
                         drawerState = drawerState,
@@ -219,7 +246,6 @@
                             hintLabelResId = R.string.volume_ringer_hint_unmute,
                             ringerMode = ringerMode,
                         )
-
                     availableModes.contains(RingerMode(RINGER_MODE_VIBRATE)) ->
                         RingerButtonViewModel(
                             imageResId = R.drawable.ic_speaker_on,
@@ -232,7 +258,6 @@
                             hintLabelResId = R.string.volume_ringer_hint_vibrate,
                             ringerMode = ringerMode,
                         )
-
                     else ->
                         RingerButtonViewModel(
                             imageResId = R.drawable.ic_speaker_on,
@@ -269,17 +294,14 @@
                             null
                         }
                     }
-
                     RINGER_MODE_SILENT ->
                         applicationContext.getString(
                             internalR.string.volume_dialog_ringer_guidance_silent
                         )
-
                     RINGER_MODE_VIBRATE ->
                         applicationContext.getString(
                             internalR.string.volume_dialog_ringer_guidance_vibrate
                         )
-
                     else ->
                         applicationContext.getString(
                             internalR.string.volume_dialog_ringer_guidance_vibrate
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/VolumeDialogLogger.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/VolumeDialogLogger.kt
index 9a3aa7e..cccf090 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/VolumeDialogLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/VolumeDialogLogger.kt
@@ -45,6 +45,52 @@
         )
     }
 
+    fun onVolumeSliderAdjustmentFinished(volume: Int, stream: Int) {
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                int1 = volume
+                int2 = stream
+            },
+            { "Volume adjusted: volume=$int1 stream=$int2" },
+        )
+    }
+
+    fun onVolumeSlidersUpdated(primaryStream: Int, floating: Collection<Int>) {
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                int1 = primaryStream
+                str1 = floating.joinToString(",") { it.toString() }
+            },
+            { "Showing streams: primary=$int1 floating=$str1" },
+        )
+    }
+
+    fun onRingerModeChanged(ringerMode: RingerMode) {
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            { int1 = ringerMode.value },
+            { "Ringer mode changed to: $int1" },
+        )
+    }
+
+    fun onRingerDrawerAvailable(modes: List<RingerMode>) {
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            { str1 = modes.joinToString(",") { it.value.toString() } },
+            { "Ringer drawer available with modes: $str1" },
+        )
+    }
+
+    fun onRingerDrawerUnavailable() {
+        logBuffer.log(TAG, LogLevel.DEBUG, {}, { "Ringer drawer unavailable" })
+    }
+
     fun onCurrentRingerModeIsUnsupported(ringerMode: RingerMode) {
         logBuffer.log(
             TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
index 3b964fd..d403024 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderStateModel
 import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderViewModel
 import com.google.android.material.slider.Slider
+import com.google.android.material.slider.Slider.OnSliderTouchListener
 import javax.inject.Inject
 import kotlin.math.roundToInt
 import kotlinx.coroutines.CoroutineScope
@@ -68,6 +69,15 @@
         sliderView.addOnChangeListener { _, value, fromUser ->
             viewModel.setStreamVolume(value.roundToInt(), fromUser)
         }
+        sliderView.addOnSliderTouchListener(
+            object : OnSliderTouchListener {
+                override fun onStartTrackingTouch(slider: Slider) {}
+
+                override fun onStopTrackingTouch(slider: Slider) {
+                    viewModel.onStreamChangeFinished(slider.value.roundToInt())
+                }
+            }
+        )
 
         viewModel.isDisabledByZenMode.onEach { sliderView.isEnabled = !it }.launchIn(this)
         viewModel.state
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt
index 71fe22b..9cf02f2 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt
@@ -82,10 +82,6 @@
         ringerMode: RingerMode?,
     ): Int {
         val isStreamOffline = level == 0 || isMuted
-        when (ringerMode?.value) {
-            AudioManager.RINGER_MODE_VIBRATE -> return R.drawable.ic_volume_ringer_vibrate
-            AudioManager.RINGER_MODE_SILENT -> return R.drawable.ic_ring_volume_off
-        }
         if (isRoutedToBluetooth) {
             return if (stream == AudioManager.STREAM_VOICE_CALL) {
                 R.drawable.ic_volume_bt_sco
@@ -98,29 +94,39 @@
             }
         }
 
+        val isLevelLow = level < (levelMax + levelMin) / 2
         return if (isStreamOffline) {
+            val ringerOfflineIcon =
+                when (ringerMode?.value) {
+                    AudioManager.RINGER_MODE_VIBRATE -> return R.drawable.ic_volume_ringer_vibrate
+                    AudioManager.RINGER_MODE_SILENT -> return R.drawable.ic_ring_volume_off
+                    else -> null
+                }
             when (stream) {
                 AudioManager.STREAM_MUSIC -> R.drawable.ic_volume_media_mute
-                AudioManager.STREAM_NOTIFICATION -> R.drawable.ic_volume_ringer_mute
+                AudioManager.STREAM_NOTIFICATION ->
+                    ringerOfflineIcon ?: R.drawable.ic_volume_ringer_mute
+                AudioManager.STREAM_RING -> ringerOfflineIcon ?: R.drawable.ic_volume_ringer_vibrate
                 AudioManager.STREAM_ALARM -> R.drawable.ic_volume_alarm_mute
                 AudioManager.STREAM_SYSTEM -> R.drawable.ic_volume_system_mute
                 else -> null
-            } ?: getIconForStream(stream)
-        } else {
-            if (level < (levelMax + levelMin) / 2) {
-                // This icon is different on TV
-                R.drawable.ic_volume_media_low
-            } else {
-                getIconForStream(stream)
             }
-        }
+        } else {
+            null
+        } ?: getIconForStream(stream = stream, isLevelLow = isLevelLow)
     }
 
     @DrawableRes
-    private fun getIconForStream(stream: Int): Int {
+    private fun getIconForStream(stream: Int, isLevelLow: Boolean): Int {
         return when (stream) {
             AudioManager.STREAM_ACCESSIBILITY -> R.drawable.ic_volume_accessibility
-            AudioManager.STREAM_MUSIC -> R.drawable.ic_volume_media
+            AudioManager.STREAM_MUSIC ->
+                if (isLevelLow) {
+                    // This icon is different on TV
+                    R.drawable.ic_volume_media_low
+                } else {
+                    R.drawable.ic_volume_media
+                }
             AudioManager.STREAM_RING -> R.drawable.ic_ring_volume
             AudioManager.STREAM_NOTIFICATION -> R.drawable.ic_volume_ringer
             AudioManager.STREAM_ALARM -> R.drawable.ic_alarm
@@ -135,7 +141,9 @@
      * affect the [stream]
      */
     private fun ringerModeForStream(stream: Int): Flow<RingerMode?> {
-        return if (stream == AudioManager.STREAM_RING) {
+        return if (
+            stream == AudioManager.STREAM_RING || stream == AudioManager.STREAM_NOTIFICATION
+        ) {
             audioVolumeInteractor.ringerMode
         } else {
             flowOf(null)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
index d999910..89dd035 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
@@ -20,9 +20,11 @@
 import com.android.systemui.volume.Events
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
 import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
+import com.android.systemui.volume.dialog.shared.VolumeDialogLogger
 import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel
 import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
 import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInteractor
+import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -60,6 +62,8 @@
     @VolumeDialog private val coroutineScope: CoroutineScope,
     private val volumeDialogSliderIconProvider: VolumeDialogSliderIconProvider,
     private val systemClock: SystemClock,
+    private val sliderType: VolumeDialogSliderType,
+    private val logger: VolumeDialogLogger,
 ) {
 
     private val userVolumeUpdates = MutableStateFlow<VolumeUpdate?>(null)
@@ -110,6 +114,10 @@
         }
     }
 
+    fun onStreamChangeFinished(volume: Int) {
+        logger.onVolumeSliderAdjustmentFinished(volume = volume, stream = sliderType.audioStream)
+    }
+
     private fun getTimestampMillis(): Long = systemClock.uptimeMillis()
 
     private data class VolumeUpdate(val newVolumeLevel: Int, val timestampMillis: Long)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt
index d8e6aec..344dadc 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt
@@ -18,6 +18,7 @@
 
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
+import com.android.systemui.volume.dialog.shared.VolumeDialogLogger
 import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderComponent
 import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSlidersInteractor
 import javax.inject.Inject
@@ -33,13 +34,18 @@
 @Inject
 constructor(
     @VolumeDialog coroutineScope: CoroutineScope,
-    private val slidersInteractor: VolumeDialogSlidersInteractor,
+    slidersInteractor: VolumeDialogSlidersInteractor,
     private val sliderComponentFactory: VolumeDialogSliderComponent.Factory,
+    private val volumeDialogLogger: VolumeDialogLogger,
 ) {
 
     val sliders: Flow<VolumeDialogSliderUiModel> =
         slidersInteractor.sliders
             .map { slidersModel ->
+                volumeDialogLogger.onVolumeSlidersUpdated(
+                    slidersModel.slider.audioStream,
+                    slidersModel.floatingSliders.map { it.audioStream },
+                )
                 VolumeDialogSliderUiModel(
                     sliderComponent = sliderComponentFactory.create(slidersModel.slider),
                     floatingSliderComponent =
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index 5b8d9b0..328d124 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -65,8 +65,6 @@
 ) : SliderViewModel {
 
     private val volumeChanges = MutableStateFlow<Int?>(null)
-    private val streamsAffectedByRing =
-        setOf(AudioManager.STREAM_RING, AudioManager.STREAM_NOTIFICATION)
     private val audioStream = audioStreamWrapper.audioStream
     private val iconsByStream =
         mapOf(
@@ -175,9 +173,9 @@
                     null
                 },
             a11yStateDescription =
-                if (volume == volumeRange.first) {
+                if (isMuted) {
                     context.getString(
-                        if (audioStream.value in streamsAffectedByRing) {
+                        if (isAffectedByRingerMode) {
                             if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) {
                                 R.string.volume_panel_hint_vibrate
                             } else {
@@ -226,8 +224,8 @@
 
     private fun AudioStreamModel.getIcon(ringerMode: RingerMode): Icon {
         val iconRes =
-            if (isAffectedByMute && isMuted) {
-                if (audioStream.value in streamsAffectedByRing) {
+            if (isMuted) {
+                if (isAffectedByRingerMode) {
                     if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) {
                         R.drawable.ic_volume_ringer_vibrate
                     } else {
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/GradientColorWallpaper.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/GradientColorWallpaper.kt
new file mode 100644
index 0000000..c1fb0e8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/GradientColorWallpaper.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.wallpapers
+
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.service.wallpaper.WallpaperService
+import android.util.Log
+import android.view.SurfaceHolder
+import androidx.core.graphics.toRectF
+
+/** A wallpaper that shows a static gradient color image wallpaper. */
+class GradientColorWallpaper : WallpaperService() {
+
+    override fun onCreateEngine(): Engine = GradientColorWallpaperEngine()
+
+    inner class GradientColorWallpaperEngine : Engine() {
+        init {
+            setShowForAllUsers(true)
+        }
+
+        override fun onSurfaceRedrawNeeded(surfaceHolder: SurfaceHolder) {
+            drawFrameInternal(surfaceHolder)
+        }
+
+        private fun drawFrameInternal(surfaceHolder: SurfaceHolder) {
+            val context = displayContext ?: return
+            val surface = surfaceHolder.surface
+            var canvas: Canvas? = null
+            try {
+                canvas = surface.lockHardwareCanvas()
+                val destRectF = surfaceHolder.surfaceFrame.toRectF()
+                val toColor = context.getColor(com.android.internal.R.color.materialColorPrimary)
+
+                // TODO(b/384519696): Draw the actual gradient color wallpaper instead.
+                canvas.drawRect(destRectF, Paint().apply { color = toColor })
+            } catch (exception: IllegalStateException) {
+                Log.d(TAG, "Fail to draw in the canvas", exception)
+            } finally {
+                canvas?.let { surface.unlockCanvasAndPost(it) }
+            }
+        }
+    }
+
+    private companion object {
+        const val TAG = "GradientColorWallpaper"
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
index 9f6ad56..c14cb87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
@@ -194,7 +194,7 @@
         final Runnable onSpringAnimationsEndCallback = mock(Runnable.class);
         mMenuAnimationController.setSpringAnimationsEndAction(onSpringAnimationsEndCallback);
 
-        mMenuAnimationController.flingMenuThenSpringToEdge(/* x= */ 0, /* velocityX= */
+        mMenuAnimationController.flingMenuThenSpringToEdge(new PointF(), /* velocityX= */
                 100, /* velocityY= */ 100);
         mMenuAnimationController.mPositionAnimations.values()
                 .forEach(animation -> verify((FlingAnimation) animation).addEndListener(
@@ -212,7 +212,7 @@
         final Runnable onSpringAnimationsEndCallback = mock(Runnable.class);
         mMenuAnimationController.setSpringAnimationsEndAction(onSpringAnimationsEndCallback);
 
-        mMenuAnimationController.flingMenuThenSpringToEdge(/* x= */ 0, /* velocityX= */
+        mMenuAnimationController.flingMenuThenSpringToEdge(new PointF(), /* velocityX= */
                 200, /* velocityY= */ 200);
         mMenuAnimationController.mPositionAnimations.values()
                 .forEach(animation -> verify((FlingAnimation) animation).addEndListener(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearanceTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearanceTest.java
new file mode 100644
index 0000000..146488b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearanceTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.accessibility.floatingmenu;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.graphics.Rect;
+import android.testing.TestableLooper;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link MenuViewAppearanceTest}. */
+@RunWith(AndroidJUnit4.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+public class MenuViewAppearanceTest extends SysuiTestCase {
+    static final Rect DRAGGABLE_BOUNDS = new Rect(0, 0, 10, 10);
+    static final int MENU_HEIGHT = 1;
+
+    @Test
+    public void avoidVerticalDisplayCutout_roomAbove_placesAbove() {
+        final int y = 2;
+        final Rect cutout = new Rect(0, 3, 0, 10);
+
+        final float end_y = MenuViewAppearance.avoidVerticalDisplayCutout(
+                y, MENU_HEIGHT, DRAGGABLE_BOUNDS, cutout);
+
+        assertThat(end_y + MENU_HEIGHT).isAtMost(cutout.top);
+    }
+
+    @Test
+    public void avoidVerticalDisplayCutout_roomBelow_placesBelow() {
+        final int y = 2;
+        final Rect cutout = new Rect(0, 0, 0, 5);
+
+        final float end_y = MenuViewAppearance.avoidVerticalDisplayCutout(
+                y, MENU_HEIGHT, DRAGGABLE_BOUNDS, cutout);
+
+        assertThat(end_y).isAtLeast(cutout.bottom);
+    }
+
+    @Test
+    public void avoidVerticalDisplayCutout_noRoom_noChange() {
+        final int y = 2;
+        final Rect cutout = new Rect(0, 0, 0, 10);
+
+        final float end_y = MenuViewAppearance.avoidVerticalDisplayCutout(
+                y, MENU_HEIGHT, DRAGGABLE_BOUNDS, cutout);
+
+        assertThat(end_y).isEqualTo(end_y);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index 1500340..01fc868 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -232,7 +232,6 @@
     }
 
     @Test
-    @EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
     public void triggerDismissMenuAction_callsA11yManagerEnableShortcutsForTargets() {
         final List<String> stubShortcutTargets = new ArrayList<>();
         stubShortcutTargets.add(TEST_SELECT_TO_SPEAK_COMPONENT_NAME.flattenToString());
@@ -249,45 +248,6 @@
     }
 
     @Test
-    @DisableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
-    public void triggerDismissMenuAction_matchA11yButtonTargetsResult() {
-        mMenuViewLayer.mDismissMenuAction.run();
-        verify(mSecureSettings).putStringForUser(
-                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, /* value= */ "",
-                UserHandle.USER_CURRENT);
-    }
-
-    @Test
-    @DisableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
-    public void triggerDismissMenuAction_matchEnabledA11yServicesResult() {
-        setupEnabledAccessibilityServiceList();
-
-        mMenuViewLayer.mDismissMenuAction.run();
-        final String value = Settings.Secure.getStringForUser(mSpyContext.getContentResolver(),
-                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
-                mSecureSettings.getRealUserHandle(UserHandle.USER_CURRENT));
-
-        assertThat(value).isEqualTo("");
-    }
-
-    @Test
-    @DisableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
-    public void triggerDismissMenuAction_hasHardwareKeyShortcut_keepEnabledStatus() {
-        setupEnabledAccessibilityServiceList();
-        final List<String> stubShortcutTargets = new ArrayList<>();
-        stubShortcutTargets.add(TEST_SELECT_TO_SPEAK_COMPONENT_NAME.flattenToString());
-        when(mStubAccessibilityManager.getAccessibilityShortcutTargets(
-                ShortcutConstants.UserShortcutType.HARDWARE)).thenReturn(stubShortcutTargets);
-
-        mMenuViewLayer.mDismissMenuAction.run();
-        final String value = Settings.Secure.getStringForUser(mSpyContext.getContentResolver(),
-                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
-                mSecureSettings.getRealUserHandle(UserHandle.USER_CURRENT));
-
-        assertThat(value).isEqualTo(TEST_SELECT_TO_SPEAK_COMPONENT_NAME.flattenToString());
-    }
-
-    @Test
     @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
     public void onEditAction_startsActivity() {
         mockActivityQuery(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt
deleted file mode 100644
index 134c40d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-package com.android.systemui.bouncer.data.repository
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.time.SystemClock
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class KeyguardBouncerRepositoryTest : SysuiTestCase() {
-
-    @Mock private lateinit var systemClock: SystemClock
-    @Mock private lateinit var bouncerLogger: TableLogBuffer
-
-    private val kosmos = testKosmos()
-    private val testScope = kosmos.testScope
-
-    lateinit var underTest: KeyguardBouncerRepository
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        underTest =
-            KeyguardBouncerRepositoryImpl(
-                systemClock,
-                testScope.backgroundScope,
-                bouncerLogger,
-            )
-    }
-
-    @Test
-    fun changingFlowValueTriggersLogging() =
-        testScope.runTest {
-            underTest.setPrimaryShow(true)
-            Mockito.verify(bouncerLogger)
-                .logChange(eq(""), eq("PrimaryBouncerShow"), value = eq(false), any())
-        }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 3763282..6ac20d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -881,7 +881,7 @@
     @EnableSceneContainer
     public void testIsInsideScrollableRegion_noOffset() {
         mStackScroller.setLeftTopRightBottom(0, 0, 1000, 2000);
-        mStackScroller.setScrimClippingShape(createScrimShape(100, 500, 900, 2000));
+        mStackScroller.setClippingShape(createScrimShape(100, 500, 900, 2000));
 
         MotionEvent event1 = transformEventForView(createMotionEvent(500f, 400f), mStackScroller);
         assertThat(mStackScroller.isInScrollableRegion(event1)).isFalse();
@@ -900,7 +900,7 @@
     @EnableSceneContainer
     public void testIsInsideScrollableRegion_offset() {
         mStackScroller.setLeftTopRightBottom(1000, 0, 2000, 2000);
-        mStackScroller.setScrimClippingShape(createScrimShape(100, 500, 900, 2000));
+        mStackScroller.setClippingShape(createScrimShape(100, 500, 900, 2000));
 
         MotionEvent event1 = transformEventForView(createMotionEvent(1500f, 400f), mStackScroller);
         assertThat(mStackScroller.isInScrollableRegion(event1)).isFalse();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 243be3d..437ccb6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -37,7 +37,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry
-import com.android.systemui.Flags as AconfigFlags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.SysuiTestableContext
 import com.android.systemui.battery.BatteryMeterView
@@ -52,7 +51,9 @@
 import com.android.systemui.shade.StatusBarLongPressGestureDetector
 import com.android.systemui.shade.display.StatusBarTouchShadeDisplayPolicy
 import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
 import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
 import com.android.systemui.statusbar.data.repository.fakeStatusBarContentInsetsProviderStore
 import com.android.systemui.statusbar.policy.Clock
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -62,7 +63,6 @@
 import com.android.systemui.unfold.config.UnfoldTransitionConfig
 import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
 import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel
-import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.view.ViewUtil
@@ -74,7 +74,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
@@ -82,6 +81,8 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -287,7 +288,7 @@
     }
 
     @Test
-    @DisableFlags(AconfigFlags.FLAG_STATUS_BAR_CONNECTED_DISPLAYS)
+    @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
     fun handleTouchEventFromStatusBar_touchOnPrimaryDisplay_statusBarConnectedDisplaysDisabled_shadeReceivesEvent() {
         `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
         `when`(shadeViewController.isViewEnabled).thenReturn(true)
@@ -299,10 +300,7 @@
     }
 
     @Test
-    @EnableFlags(
-        AconfigFlags.FLAG_STATUS_BAR_CONNECTED_DISPLAYS,
-        AconfigFlags.FLAG_SHADE_WINDOW_GOES_AROUND,
-    )
+    @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME, ShadeWindowGoesAround.FLAG_NAME)
     fun handleTouchEventFromStatusBar_touchOnPrimaryDisplay_statusBarConnectedDisplaysEnabled_shadeWindowGoesAroundEnabled_shadeReceivesEvent() {
         `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
         `when`(shadeViewController.isViewEnabled).thenReturn(true)
@@ -314,8 +312,8 @@
     }
 
     @Test
-    @EnableFlags(AconfigFlags.FLAG_STATUS_BAR_CONNECTED_DISPLAYS)
-    @DisableFlags(AconfigFlags.FLAG_SHADE_WINDOW_GOES_AROUND)
+    @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+    @DisableFlags(ShadeWindowGoesAround.FLAG_NAME)
     fun handleTouchEventFromStatusBar_touchOnPrimaryDisplay_statusBarConnectedDisplaysEnabled_shadeWindowGoesAroundDisabled_shadeReceivesEvent() {
         `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
         `when`(shadeViewController.isViewEnabled).thenReturn(true)
@@ -327,7 +325,7 @@
     }
 
     @Test
-    @DisableFlags(AconfigFlags.FLAG_STATUS_BAR_CONNECTED_DISPLAYS)
+    @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
     fun handleTouchEventFromStatusBar_touchOnSecondaryDisplay_statusBarConnectedDisplaysDisabled_shadeReceivesEvent() {
         `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
         `when`(shadeViewController.isViewEnabled).thenReturn(true)
@@ -339,10 +337,7 @@
     }
 
     @Test
-    @EnableFlags(
-        AconfigFlags.FLAG_STATUS_BAR_CONNECTED_DISPLAYS,
-        AconfigFlags.FLAG_SHADE_WINDOW_GOES_AROUND,
-    )
+    @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME, ShadeWindowGoesAround.FLAG_NAME)
     fun handleTouchEventFromStatusBar_touchOnSecondaryDisplay_statusBarConnectedDisplaysEnabled_shadeWindowGoesAroundEnabled_shadeReceivesEvent() {
         `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
         `when`(shadeViewController.isViewEnabled).thenReturn(true)
@@ -354,8 +349,8 @@
     }
 
     @Test
-    @EnableFlags(AconfigFlags.FLAG_STATUS_BAR_CONNECTED_DISPLAYS)
-    @DisableFlags(AconfigFlags.FLAG_SHADE_WINDOW_GOES_AROUND)
+    @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+    @DisableFlags(ShadeWindowGoesAround.FLAG_NAME)
     fun handleTouchEventFromStatusBar_touchOnSecondaryDisplay_statusBarConnectedDisplaysEnabled_shadeWindowGoesAroundDisabled_shadeDoesNotReceiveEvent() {
         `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(true)
         `when`(shadeViewController.isViewEnabled).thenReturn(true)
@@ -441,27 +436,30 @@
     }
 
     @Test
-    @EnableFlags(AconfigFlags.FLAG_SHADE_WINDOW_GOES_AROUND)
+    @EnableFlags(ShadeWindowGoesAround.FLAG_NAME)
     fun onTouch_actionDown_propagatesToDisplayPolicy() {
-        controller.onTouch(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0))
+        val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+        controller.onTouch(event)
 
-        verify(statusBarTouchShadeDisplayPolicy).onStatusBarTouched(eq(mContext.displayId))
+        verify(statusBarTouchShadeDisplayPolicy).onStatusBarTouched(eq(event), any())
     }
 
     @Test
-    @EnableFlags(AconfigFlags.FLAG_SHADE_WINDOW_GOES_AROUND)
+    @EnableFlags(ShadeWindowGoesAround.FLAG_NAME)
     fun onTouch_actionUp_notPropagatesToDisplayPolicy() {
-        controller.onTouch(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0))
+        val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)
+        controller.onTouch(event)
 
-        verify(statusBarTouchShadeDisplayPolicy, never()).onStatusBarTouched(any())
+        verify(statusBarTouchShadeDisplayPolicy, never()).onStatusBarTouched(any(), any())
     }
 
     @Test
-    @DisableFlags(AconfigFlags.FLAG_SHADE_WINDOW_GOES_AROUND)
+    @DisableFlags(ShadeWindowGoesAround.FLAG_NAME)
     fun onTouch_shadeWindowGoesAroundDisabled_notPropagatesToDisplayPolicy() {
-        controller.onTouch(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0))
+        val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+        controller.onTouch(event)
 
-        verify(statusBarTouchShadeDisplayPolicy, never()).onStatusBarTouched(any())
+        verify(statusBarTouchShadeDisplayPolicy, never()).onStatusBarTouched(eq(event), any())
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index d7456df..6c60f55 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -829,7 +829,7 @@
         testScope.runTest {
             val latest by collectLastValue(underTest.defaultDataSubId)
 
-            assertThat(latest).isEqualTo(INVALID_SUBSCRIPTION_ID)
+            assertThat(latest).isEqualTo(null)
 
             val intent2 =
                 Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
@@ -856,6 +856,31 @@
         }
 
     @Test
+    fun defaultDataSubId_filtersOutInvalidSubIds() =
+        testScope.runTest {
+            subscriptionManagerProxy.defaultDataSubId = INVALID_SUBSCRIPTION_ID
+            val latest by collectLastValue(underTest.defaultDataSubId)
+
+            assertThat(latest).isNull()
+        }
+
+    @Test
+    fun defaultDataSubId_filtersOutInvalidSubIds_fromValidToInvalid() =
+        testScope.runTest {
+            subscriptionManagerProxy.defaultDataSubId = 2
+            val latest by collectLastValue(underTest.defaultDataSubId)
+
+            assertThat(latest).isEqualTo(2)
+
+            val intent =
+                Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+                    .putExtra(PhoneConstants.SUBSCRIPTION_KEY, INVALID_SUBSCRIPTION_ID)
+            fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent)
+
+            assertThat(latest).isNull()
+        }
+
+    @Test
     fun defaultDataSubId_fetchesCurrentOnRestart() =
         testScope.runTest {
             subscriptionManagerProxy.defaultDataSubId = 2
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt
index 703e2d1..f95f957 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt
@@ -23,9 +23,18 @@
     override val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
     private val _primaryBouncerStartingToHide = MutableStateFlow(false)
     override val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
-    private val _primaryBouncerDisappearAnimation = MutableStateFlow<Runnable?>(null)
     override val primaryBouncerStartingDisappearAnimation =
-        _primaryBouncerDisappearAnimation.asStateFlow()
+        MutableSharedFlow<Runnable?>(extraBufferCapacity = 2, replay = 1)
+
+    override fun isPrimaryBouncerStartingDisappearAnimation(): Boolean {
+        val replayCache = primaryBouncerStartingDisappearAnimation.replayCache
+        return if (!replayCache.isEmpty()) {
+            replayCache.last() != null
+        } else {
+            false
+        }
+    }
+
     private val _primaryBouncerScrimmed = MutableStateFlow(false)
     override val primaryBouncerScrimmed = _primaryBouncerScrimmed.asStateFlow()
     private val _panelExpansionAmount = MutableStateFlow(KeyguardBouncerConstants.EXPANSION_HIDDEN)
@@ -53,6 +62,8 @@
         MutableStateFlow(KeyguardSecurityModel.SecurityMode.Invalid)
     override var bouncerDismissActionModel: BouncerDismissActionModel? = null
 
+    override fun isDebuggable() = true
+
     override fun setPrimaryScrimmed(isScrimmed: Boolean) {
         _primaryBouncerScrimmed.value = isScrimmed
     }
@@ -74,7 +85,7 @@
     }
 
     override fun setPrimaryStartDisappearAnimation(runnable: Runnable?) {
-        _primaryBouncerDisappearAnimation.value = runnable
+        primaryBouncerStartingDisappearAnimation.tryEmit(runnable)
     }
 
     override fun setPanelExpansion(panelExpansion: Float) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
index 4870497..e30e920 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
@@ -33,10 +33,7 @@
 @SysUISingleton
 class FakeConfigurationRepository @Inject constructor() : ConfigurationRepository {
     private val _onAnyConfigurationChange =
-        MutableSharedFlow<Unit>(
-            replay = 1,
-            onBufferOverflow = BufferOverflow.DROP_OLDEST,
-        )
+        MutableSharedFlow<Unit>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
     override val onAnyConfigurationChange: Flow<Unit> = _onAnyConfigurationChange.asSharedFlow()
 
     private val _onConfigurationChange =
@@ -53,7 +50,7 @@
         get() = _onMovedToDisplay
 
     private val _scaleForResolution = MutableStateFlow(1f)
-    override val scaleForResolution: Flow<Float> = _scaleForResolution.asStateFlow()
+    override val scaleForResolution: StateFlow<Float> = _scaleForResolution.asStateFlow()
 
     private val pixelSizes = mutableMapOf<Int, MutableStateFlow<Int>>()
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/compose/Snapshot.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/compose/Snapshot.kt
index fb6699c..91f1e1c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/compose/Snapshot.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/compose/Snapshot.kt
@@ -17,18 +17,16 @@
 package com.android.systemui.compose
 
 import androidx.compose.runtime.snapshots.Snapshot
-import com.android.systemui.kosmos.runCurrent
 import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 
 /**
  * Runs the given test [block] in a [TestScope] that's set up such that the Compose snapshot state
- * is settled eagerly. This is the Compose equivalent to using an [UnconfinedTestDispatcher] or
- * using [runCurrent] a lot.
+ * writes are properly applied to the global snapshot. This is for instance necessary if your test
+ * is using `snapshotFlow {}` or any other mechanism that is observing the global snapshot.
  *
- * Note that this shouldn't be needed or used in a Compose test environment.
+ * Note that this isn't needed in a Compose test environment, e.g. if you use the
+ * `Compose(Content)TestRule`.
  */
 fun TestScope.runTestWithSnapshots(block: suspend TestScope.() -> Unit) {
     val handle = Snapshot.registerGlobalWriteObserver { Snapshot.sendApplyNotifications() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt
index 0462848..4f3b8f3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt
@@ -20,12 +20,14 @@
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.shade.ui.viewmodel.shadeHeaderViewModelFactory
+import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
 
 val Kosmos.quickSettingsShadeOverlayContentViewModel: QuickSettingsShadeOverlayContentViewModel by
     Kosmos.Fixture {
         QuickSettingsShadeOverlayContentViewModel(
             shadeInteractor = shadeInteractor,
             sceneInteractor = sceneInteractor,
+            notificationStackAppearanceInteractor = notificationStackAppearanceInteractor,
             shadeHeaderViewModelFactory = shadeHeaderViewModelFactory,
             quickSettingsContainerViewModelFactory = quickSettingsContainerViewModelFactory,
         )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt
index 636cb37..aaef27d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt
@@ -23,7 +23,11 @@
 import com.android.systemui.shade.display.AnyExternalShadeDisplayPolicy
 import com.android.systemui.shade.display.DefaultDisplayShadePolicy
 import com.android.systemui.shade.display.ShadeDisplayPolicy
+import com.android.systemui.shade.display.ShadeExpansionIntent
 import com.android.systemui.shade.display.StatusBarTouchShadeDisplayPolicy
+import com.android.systemui.shade.domain.interactor.notificationElement
+import com.android.systemui.shade.domain.interactor.qsElement
+import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.util.settings.fakeGlobalSettings
 
 val Kosmos.defaultShadeDisplayPolicy: DefaultDisplayShadePolicy by
@@ -37,16 +41,20 @@
         )
     }
 
-val Kosmos.focusBasedShadeDisplayPolicy: StatusBarTouchShadeDisplayPolicy by
+val Kosmos.statusBarTouchShadeDisplayPolicy: StatusBarTouchShadeDisplayPolicy by
     Kosmos.Fixture {
         StatusBarTouchShadeDisplayPolicy(
             displayRepository = displayRepository,
             backgroundScope = testScope.backgroundScope,
             keyguardRepository = keyguardRepository,
             shadeOnDefaultDisplayWhenLocked = false,
+            shadeInteractor = { shadeInteractor },
+            notificationElement = { notificationElement },
+            qsShadeElement = { qsElement },
         )
     }
-
+val Kosmos.shadeExpansionIntent: ShadeExpansionIntent by
+    Kosmos.Fixture { statusBarTouchShadeDisplayPolicy }
 val Kosmos.shadeDisplaysRepository: MutableShadeDisplaysRepository by
     Kosmos.Fixture {
         ShadeDisplaysRepositoryImpl(
@@ -62,7 +70,7 @@
         setOf(
             defaultShadeDisplayPolicy,
             anyExternalShadeDisplayPolicy,
-            focusBasedShadeDisplayPolicy,
+            statusBarTouchShadeDisplayPolicy,
         )
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.kt
index 6e44df8..923de2d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.kt
@@ -23,6 +23,7 @@
 import com.android.systemui.shade.ShadeDisplayChangeLatencyTracker
 import com.android.systemui.shade.ShadeWindowLayoutParams
 import com.android.systemui.shade.data.repository.fakeShadeDisplaysRepository
+import com.android.systemui.shade.data.repository.shadeExpansionIntent
 import java.util.Optional
 import org.mockito.kotlin.any
 import org.mockito.kotlin.mock
@@ -49,5 +50,6 @@
             testScope.backgroundScope.coroutineContext,
             mockedShadeDisplayChangeLatencyTracker,
             Optional.of(shadeExpandedStateInteractor),
+            shadeExpansionIntent,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
index 1dc7229..32a3050 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
@@ -31,7 +31,6 @@
 import com.android.systemui.statusbar.policy.data.repository.userSetupRepository
 import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
 import com.android.systemui.user.domain.interactor.userSwitcherInteractor
-import org.mockito.kotlin.mock
 
 var Kosmos.baseShadeInteractor: BaseShadeInteractor by
     Kosmos.Fixture {
@@ -73,7 +72,19 @@
             shadeModeInteractor = shadeModeInteractor,
         )
     }
-var Kosmos.mockShadeInteractor: ShadeInteractor by Kosmos.Fixture { mock() }
+var Kosmos.notificationElement: NotificationShadeElement by
+    Kosmos.Fixture {
+        NotificationShadeElement(shadeInteractor, testScope.backgroundScope.coroutineContext)
+    }
+var Kosmos.qsElement: QSShadeElement by
+    Kosmos.Fixture { QSShadeElement(shadeInteractor, testScope.backgroundScope.coroutineContext) }
 val Kosmos.shadeExpandedStateInteractor by
-    Kosmos.Fixture { ShadeExpandedStateInteractorImpl(shadeInteractor, testScope.backgroundScope) }
+    Kosmos.Fixture {
+        ShadeExpandedStateInteractorImpl(
+            shadeInteractor,
+            testScope.backgroundScope,
+            notificationElement,
+            qsElement,
+        )
+    }
 val Kosmos.fakeShadeExpandedStateInteractor by Kosmos.Fixture { FakeShadeExpandedStateInteractor() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt
index 01cac4c..99323db 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt
@@ -31,3 +31,8 @@
         shadeInteractor = shadeInteractor,
     )
 }
+val Kosmos.footerViewModelFactory: FooterViewModel.Factory by Fixture {
+    object : FooterViewModel.Factory {
+        override fun create() = footerViewModel
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
index c3bc744..fbc2a21 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
@@ -24,7 +24,7 @@
 import com.android.systemui.statusbar.domain.interactor.remoteInputInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
 import com.android.systemui.statusbar.notification.emptyshade.ui.viewmodel.emptyShadeViewModelFactory
-import com.android.systemui.statusbar.notification.footer.ui.viewmodel.footerViewModel
+import com.android.systemui.statusbar.notification.footer.ui.viewmodel.footerViewModelFactory
 import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.notificationShelfViewModel
 import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackInteractor
@@ -35,7 +35,7 @@
     NotificationListViewModel(
         notificationShelfViewModel,
         hideListViewModel,
-        Optional.of(footerViewModel),
+        footerViewModelFactory,
         emptyShadeViewModelFactory,
         Optional.of(notificationListLoggerViewModel),
         activeNotificationsInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeCarrierConfigRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeCarrierConfigRepository.kt
index adf6ca1..eebfca7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeCarrierConfigRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeCarrierConfigRepository.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.pipeline.mobile.data.repository
 
 import android.os.PersistableBundle
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
 
 class FakeCarrierConfigRepository : CarrierConfigRepository {
@@ -24,8 +25,12 @@
 
     val configsById = mutableMapOf<Int, SystemUiCarrierConfig>()
 
-    override fun getOrCreateConfigForSubId(subId: Int): SystemUiCarrierConfig =
-        configsById.getOrPut(subId) { SystemUiCarrierConfig(subId, createDefaultTestConfig()) }
+    override fun getOrCreateConfigForSubId(maybeSubId: Int?): SystemUiCarrierConfig {
+        val subId = maybeSubId ?: INVALID_SUBSCRIPTION_ID
+        return configsById.getOrPut(subId) {
+            SystemUiCarrierConfig(subId, createDefaultTestConfig())
+        }
+    }
 }
 
 val CarrierConfigRepository.fake
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
index 3b8adb4..352f6cf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
@@ -55,7 +55,7 @@
 
     override val filteredSubscriptions = MutableStateFlow<List<SubscriptionModel>>(listOf())
 
-    override val defaultDataSubId = MutableStateFlow(DEFAULT_DATA_SUB_ID)
+    override val defaultDataSubId: MutableStateFlow<Int?> = MutableStateFlow(DEFAULT_DATA_SUB_ID)
 
     private val _activeDataConnectionHasDataEnabled = MutableStateFlow(false)
     override val activeDataConnectionHasDataEnabled = _activeDataConnectionHasDataEnabled
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt
index 4fda95b..9f3150f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt
@@ -23,6 +23,8 @@
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.statusbar.notification.domain.interactor.notificationsSoundPolicyInteractor
 import com.android.systemui.statusbar.policy.configurationController
+import com.android.systemui.util.time.fakeSystemClock
+import com.android.systemui.util.time.systemClock
 import com.android.systemui.volume.dialog.domain.interactor.volumeDialogVisibilityInteractor
 import com.android.systemui.volume.dialog.ringer.domain.volumeDialogRingerInteractor
 import com.android.systemui.volume.dialog.shared.volumeDialogLogger
@@ -39,5 +41,6 @@
             volumeDialogLogger = volumeDialogLogger,
             visibilityInteractor = volumeDialogVisibilityInteractor,
             configurationController = configurationController,
+            systemClock = fakeSystemClock,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponentKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponentKosmos.kt
index 4f79f7b4..cb38cc3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponentKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponentKosmos.kt
@@ -18,6 +18,7 @@
 
 import android.content.applicationContext
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.plugins.volumeDialogController
 import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
@@ -74,6 +75,7 @@
     volumeDialogSliderType = type
     applicationContext = parentKosmos.applicationContext
     testScope = parentKosmos.testScope
+    testDispatcher = parentKosmos.testDispatcher
 
     volumeDialogController = parentKosmos.volumeDialogController
     mediaControllerInteractor = parentKosmos.mediaControllerInteractor
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModelKosmos.kt
index 63cd440..b26081c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModelKosmos.kt
@@ -20,15 +20,19 @@
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.util.time.systemClock
 import com.android.systemui.volume.dialog.domain.interactor.volumeDialogVisibilityInteractor
+import com.android.systemui.volume.dialog.shared.volumeDialogLogger
 import com.android.systemui.volume.dialog.sliders.domain.interactor.volumeDialogSliderInteractor
+import com.android.systemui.volume.dialog.sliders.domain.model.volumeDialogSliderType
 
 val Kosmos.volumeDialogSliderViewModel by
     Kosmos.Fixture {
         VolumeDialogSliderViewModel(
+            sliderType = volumeDialogSliderType,
             interactor = volumeDialogSliderInteractor,
             visibilityInteractor = volumeDialogVisibilityInteractor,
             coroutineScope = applicationCoroutineScope,
             volumeDialogSliderIconProvider = volumeDialogSliderIconProvider,
             systemClock = systemClock,
+            logger = volumeDialogLogger,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModelKosmos.kt
index 5531f76..8fb60fd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModelKosmos.kt
@@ -18,6 +18,7 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.volume.dialog.shared.volumeDialogLogger
 import com.android.systemui.volume.dialog.sliders.dagger.volumeDialogSliderComponentFactory
 import com.android.systemui.volume.dialog.sliders.domain.interactor.volumeDialogSlidersInteractor
 
@@ -27,5 +28,6 @@
             applicationCoroutineScope,
             volumeDialogSlidersInteractor,
             volumeDialogSliderComponentFactory,
+            volumeDialogLogger,
         )
     }
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/BuildScope.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/BuildScope.kt
index bd2173c..c9fae70 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/BuildScope.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/BuildScope.kt
@@ -100,7 +100,7 @@
      * observation of new emissions. It will however *not* cancel any running effects from previous
      * emissions. To achieve this behavior, use [launchScope] or [asyncScope] to create a child
      * build scope:
-     * ``` kotlin
+     * ```
      *   val job = launchScope {
      *       events.observe { x ->
      *           launchEffect { longRunningEffect(x) }
@@ -141,7 +141,7 @@
      *
      * By default, [builder] is only running while the returned [Events] is being
      * [observed][observe]. If you want it to run at all times, simply add a no-op observer:
-     * ``` kotlin
+     * ```
      *   events { ... }.apply { observe() }
      * ```
      */
@@ -158,7 +158,7 @@
      *
      * By default, [builder] is only running while the returned [Events] is being
      * [observed][observe]. If you want it to run at all times, simply add a no-op observer:
-     * ``` kotlin
+     * ```
      *   events { ... }.apply { observe() }
      * ```
      *
@@ -196,7 +196,7 @@
      * outside of the current Kairos transaction; when [transform] returns, the returned value is
      * emitted from the result [Events] in a new transaction.
      *
-     * ``` kotlin
+     * ```
      *     fun <A, B> Events<A>.mapAsyncLatest(transform: suspend (A) -> B): Events<B> =
      *         mapLatestBuild { a -> asyncEvent { transform(a) } }.flatten()
      * ```
@@ -571,7 +571,7 @@
 
     /**
      * Shorthand for:
-     * ``` kotlin
+     * ```
      * flow.toEvents().holdState(initialValue)
      * ```
      */
@@ -579,7 +579,7 @@
 
     /**
      * Shorthand for:
-     * ``` kotlin
+     * ```
      * flow.scan(initialValue, operation).toEvents().holdState(initialValue)
      * ```
      */
@@ -588,7 +588,7 @@
 
     /**
      * Shorthand for:
-     * ``` kotlin
+     * ```
      * flow.scan(initialValue) { a, f -> f(a) }.toEvents().holdState(initialValue)
      * ```
      */
@@ -665,7 +665,7 @@
      * be used to make further modifications to the Kairos network, and/or perform side-effects via
      * [effect].
      *
-     * ``` kotlin
+     * ```
      *     fun <A> State<A>.observeBuild(block: BuildScope.(A) -> Unit = {}): Job = launchScope {
      *         block(sample())
      *         changes.observeBuild(block)
@@ -698,7 +698,7 @@
  * outside of the current Kairos transaction; when it completes, the returned [Events] emits in a
  * new transaction.
  *
- * ``` kotlin
+ * ```
  *   fun <A> BuildScope.asyncEvent(block: suspend () -> A): Events<A> =
  *       events { emit(block()) }.apply { observe() }
  * ```
@@ -719,7 +719,7 @@
  * executed if this [BuildScope] is still active by that time. It can be deactivated due to a
  * -Latest combinator, for example.
  *
- * ``` kotlin
+ * ```
  *   fun BuildScope.effect(
  *       context: CoroutineContext = EmptyCoroutineContext,
  *       block: EffectScope.() -> Unit,
@@ -740,7 +740,7 @@
  * done because the current [BuildScope] might be deactivated within this transaction, perhaps due
  * to a -Latest combinator. If this happens, then the coroutine will never actually be started.
  *
- * ``` kotlin
+ * ```
  *   fun BuildScope.launchEffect(block: suspend KairosScope.() -> Unit): Job =
  *       effect { effectCoroutineScope.launch { block() } }
  * ```
@@ -757,7 +757,7 @@
  * to a -Latest combinator. If this happens, then the coroutine will never actually be started.
  *
  * Shorthand for:
- * ``` kotlin
+ * ```
  *   fun <R> BuildScope.asyncEffect(block: suspend KairosScope.() -> R): Deferred<R> =
  *       CompletableDeferred<R>.apply {
  *               effect { effectCoroutineScope.launch { complete(block()) } }
@@ -789,7 +789,7 @@
  *
  * By default, [builder] is only running while the returned [Events] is being
  * [observed][BuildScope.observe]. If you want it to run at all times, simply add a no-op observer:
- * ``` kotlin
+ * ```
  * events { ... }.apply { observe() }
  * ```
  *
@@ -813,7 +813,7 @@
  *
  * By default, [builder] is only running while the returned [Events] is being
  * [observed][BuildScope.observe]. If you want it to run at all times, simply add a no-op observer:
- * ``` kotlin
+ * ```
  * events { ... }.apply { observe() }
  * ```
  *
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Events.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Events.kt
index 8f468c1..1a13773 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Events.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Events.kt
@@ -105,7 +105,7 @@
  *
  * Useful for recursive definitions.
  *
- * ``` kotlin
+ * ```
  *   fun <A> Lazy<Events<A>>.defer() = deferredEvents { value }
  * ```
  *
@@ -122,7 +122,7 @@
  *
  * Useful for recursive definitions.
  *
- * ``` kotlin
+ * ```
  *   fun <A> DeferredValue<Events<A>>.defer() = deferredEvents { get() }
  * ```
  *
@@ -160,7 +160,7 @@
  * Returns an [Events] that contains only the non-null results of applying [transform] to each value
  * of the original [Events].
  *
- * ``` kotlin
+ * ```
  *  fun <A> Events<A>.mapNotNull(transform: TransactionScope.(A) -> B?): Events<B> =
  *      mapMaybe { if (it == null) absent else present(it) }
  * ```
@@ -201,7 +201,7 @@
  * Returns an [Events] that invokes [action] before each value of the original [Events] is emitted.
  * Useful for logging and debugging.
  *
- * ``` kotlin
+ * ```
  *   fun <A> Events<A>.onEach(action: TransactionScope.(A) -> Unit): Events<A> =
  *       map { it.also { action(it) } }
  * ```
@@ -220,7 +220,7 @@
  * Splits an [Events] of pairs into a pair of [Events], where each returned [Events] emits half of
  * the original.
  *
- * ``` kotlin
+ * ```
  *   fun <A, B> Events<Pair<A, B>>.unzip(): Pair<Events<A>, Events<B>> {
  *       val lefts = map { it.first }
  *       val rights = map { it.second }
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Filter.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Filter.kt
index 8ca5ac8..d412b84 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Filter.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Filter.kt
@@ -29,7 +29,7 @@
 /**
  * Returns an [Events] containing only values of the original [Events] that are not null.
  *
- * ``` kotlin
+ * ```
  *  fun <A> Events<A?>.filterNotNull(): Events<A> = mapNotNull { it }
  * ```
  *
@@ -41,7 +41,7 @@
 /**
  * Returns an [Events] containing only values of the original [Events] that are instances of [A].
  *
- * ``` kotlin
+ * ```
  *   inline fun <reified A> Events<*>.filterIsInstance(): Events<A> =
  *       mapNotNull { it as? A }
  * ```
@@ -55,7 +55,7 @@
 /**
  * Returns an [Events] containing only values of the original [Events] that are present.
  *
- * ``` kotlin
+ * ```
  *  fun <A> Events<Maybe<A>>.filterPresent(): Events<A> = mapMaybe { it }
  * ```
  *
@@ -69,7 +69,7 @@
  * Returns an [Events] containing only values of the original [Events] that satisfy the given
  * [predicate].
  *
- * ``` kotlin
+ * ```
  *   fun <A> Events<A>.filter(predicate: TransactionScope.(A) -> Boolean): Events<A> =
  *       mapMaybe { if (predicate(it)) present(it) else absent }
  * ```
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/GroupBy.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/GroupBy.kt
index 45da34a..27fc1b4 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/GroupBy.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/GroupBy.kt
@@ -63,7 +63,7 @@
  * downstream [Events]. The downstream [Events] are associated with a [key][K], which is derived
  * from each emission of the original [Events] via [extractKey].
  *
- * ``` kotlin
+ * ```
  *   fun <K, A> Events<A>.groupBy(
  *       numKeys: Int? = null,
  *       extractKey: TransactionScope.(A) -> K,
@@ -108,7 +108,7 @@
  * Using this is equivalent to `upstream.filter(predicate) to upstream.filter { !predicate(it) }`
  * but is more efficient; specifically, [partition] will only invoke [predicate] once per element.
  *
- * ``` kotlin
+ * ```
  *   fun <A> Events<A>.partition(
  *       predicate: TransactionScope.(A) -> Boolean
  *   ): Pair<Events<A>, Events<A>> =
@@ -133,7 +133,7 @@
  * [First]s and once for [Second]s, but is slightly more efficient; specifically, the
  * [filterIsInstance] check is only performed once per element.
  *
- * ``` kotlin
+ * ```
  *   fun <A, B> Events<Either<A, B>>.partitionEither(): Pair<Events<A>, Events<B>> =
  *     map { it.asThese() }.partitionThese()
  * ```
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Incremental.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Incremental.kt
index d88ae3b8..02941bd 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Incremental.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Incremental.kt
@@ -62,7 +62,7 @@
  *
  * Useful for recursive definitions.
  *
- * ``` kotlin
+ * ```
  *   fun <A> Lazy<Incremental<K, V>>.defer() = deferredIncremental { value }
  * ```
  */
@@ -78,7 +78,7 @@
  *
  * Useful for recursive definitions.
  *
- * ``` kotlin
+ * ```
  *   fun <A> DeferredValue<Incremental<K, V>>.defer() = deferredIncremental { get() }
  * ```
  */
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Merge.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Merge.kt
index de9dca4..cc4ce53 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Merge.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Merge.kt
@@ -33,7 +33,7 @@
  * function is used to combine coincident emissions to produce the result value to be emitted by the
  * merged [Events].
  *
- * ``` kotlin
+ * ```
  * fun <A> Events<A>.mergeWith(
  *     other: Events<A>,
  *     transformCoincidence: TransactionScope.(A, A) -> A = { a, _ -> a },
@@ -62,7 +62,7 @@
  * Merges the given [Events] into a single [Events] that emits events from all. All coincident
  * emissions are collected into the emitted [List], preserving the input ordering.
  *
- * ``` kotlin
+ * ```
  *   fun <A> merge(vararg events: Events<A>): Events<List<A>> = events.asIterable().merge()
  * ```
  *
@@ -76,7 +76,7 @@
  * Merges the given [Events] into a single [Events] that emits events from all. In the case of
  * coincident emissions, the emission from the left-most [Events] is emitted.
  *
- * ``` kotlin
+ * ```
  *   fun <A> mergeLeft(vararg events: Events<A>): Events<A> = events.asIterable().mergeLeft()
  * ```
  *
@@ -92,7 +92,7 @@
  * function is used to combine coincident emissions to produce the result value to be emitted by the
  * merged [Events].
  *
- * ``` kotlin
+ * ```
  *   fun <A> merge(vararg events: Events<A>, transformCoincidence: (A, A) -> A): Events<A> =
  *       merge(*events).map { l -> l.reduce(transformCoincidence) }
  * ```
@@ -117,7 +117,7 @@
  * coincident emissions, the emission from the left-most [Events] is emitted.
  *
  * Semantically equivalent to the following definition:
- * ``` kotlin
+ * ```
  *   fun <A> Iterable<Events<A>>.mergeLeft(): Events<A> =
  *       merge().mapCheap { it.first() }
  * ```
@@ -135,7 +135,7 @@
  * Creates a new [Events] that emits events from all given [Events]. All simultaneous emissions are
  * collected into the emitted [List], preserving the input ordering.
  *
- * ``` kotlin
+ * ```
  *   fun <A> Sequence<Events<A>>.merge(): Events<List<A>> = asIterable().merge()
  * ```
  *
@@ -148,7 +148,7 @@
  * collected into the emitted [Map], and are given the same key of the associated [Events] in the
  * input [Map].
  *
- * ``` kotlin
+ * ```
  *   fun <K, A> Map<K, Events<A>>.merge(): Events<Map<K, A>> =
  *       asSequence()
  *           .map { (k, events) -> events.map { a -> k to a } }
@@ -173,7 +173,7 @@
  * [Map.applyPatch][com.android.systemui.kairos.util.applyPatch].
  *
  * Conceptually this is equivalent to:
- * ``` kotlin
+ * ```
  *   fun <K, V> State<Map<K, V>>.mergeEventsIncrementally(): Events<Map<K, V>> =
  *       map { it.merge() }.switchEvents()
  * ```
@@ -218,7 +218,7 @@
  * [Map.applyPatch][com.android.systemui.kairos.util.applyPatch].
  *
  * Conceptually this is equivalent to:
- * ``` kotlin
+ * ```
  *   fun <K, V> State<Map<K, V>>.mergeEventsIncrementallyPromptly(): Events<Map<K, V>> =
  *       map { it.merge() }.switchEventsPromptly()
  * ```
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/State.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/State.kt
index 22ca83c..e8b005e 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/State.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/State.kt
@@ -76,7 +76,7 @@
  *
  * Useful for recursive definitions.
  *
- * ``` kotlin
+ * ```
  *   fun <A> Lazy<State<A>>.defer() = deferredState { value }
  * ```
  */
@@ -91,7 +91,7 @@
  *
  * Useful for recursive definitions.
  *
- * ``` kotlin
+ * ```
  *   fun <A> DeferredValue<State<A>>.defer() = deferredState { get() }
  * ```
  */
@@ -150,7 +150,7 @@
  * Splits a [State] of pairs into a pair of [Events][State], where each returned [State] holds half
  * of the original.
  *
- * ``` kotlin
+ * ```
  *   fun <A, B> State<Pair<A, B>>.unzip(): Pair<State<A>, State<B>> {
  *       val first = map { it.first }
  *       val second = map { it.second }
@@ -186,7 +186,7 @@
 /**
  * Returns a [State] that behaves like the current value of the original [State].
  *
- * ``` kotlin
+ * ```
  *   fun <A> State<State<A>>.flatten() = flatMap { it }
  * ```
  *
@@ -201,7 +201,7 @@
  * recent value is used.
  *
  * Effectively equivalent to:
- * ``` kotlin
+ * ```
  *     ConflatedMutableEvents(kairosNetwork).holdState(initialValue)
  * ```
  */
@@ -328,7 +328,7 @@
  * Like [changes] but also includes the old value of this [State].
  *
  * Shorthand for:
- * ``` kotlin
+ * ```
  *     stateChanges.map { WithPrev(previousValue = sample(), newValue = it) }
  * ```
  */
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/StateScope.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/StateScope.kt
index faeffe8..8020896 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/StateScope.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/StateScope.kt
@@ -116,7 +116,7 @@
      * [Events] emitted from this, following the patch rules outlined in
      * [Map.applyPatch][com.android.systemui.kairos.util.applyPatch].
      *
-     * ``` kotlin
+     * ```
      *   fun <K, V> Events<MapPatch<K, Events<V>>>.mergeEventsIncrementally(
      *     initialEvents: DeferredValue<Map<K, Events<V>>>,
      *   ): Events<Map<K, V>> =
@@ -135,7 +135,7 @@
      * [Events] emitted from this, following the patch rules outlined in
      * [Map.applyPatch][com.android.systemui.kairos.util.applyPatch].
      *
-     * ``` kotlin
+     * ```
      *   fun <K, V> Events<MapPatch<K, Events<V>>>.mergeEventsIncrementallyPromptly(
      *     initialEvents: DeferredValue<Map<K, Events<V>>>,
      *   ): Events<Map<K, V>> =
@@ -155,7 +155,7 @@
      * [Events] emitted from this, following the patch rules outlined in
      * [Map.applyPatch][com.android.systemui.kairos.util.applyPatch].
      *
-     * ``` kotlin
+     * ```
      *   fun <K, V> Events<MapPatch<K, Events<V>>>.mergeEventsIncrementally(
      *     initialEvents: Map<K, Events<V>>,
      *   ): Events<Map<K, V>> =
@@ -174,7 +174,7 @@
      * [Events] emitted from this, following the patch rules outlined in
      * [Map.applyPatch][com.android.systemui.kairos.util.applyPatch].
      *
-     * ``` kotlin
+     * ```
      *   fun <K, V> Events<MapPatch<K, Events<V>>>.mergeEventsIncrementallyPromptly(
      *     initialEvents: Map<K, Events<V>>,
      *   ): Events<Map<K, V>> =
@@ -220,7 +220,7 @@
      * [mapLatestStateful], accumulation is not stopped with each subsequent emission of the
      * original [Events].
      *
-     * ``` kotlin
+     * ```
      *   fun <A, B> Events<A>.mapStateful(transform: StateScope.(A) -> B): Events<B> =
      *       map { statefully { transform(it) } }.applyStatefuls()
      * ```
@@ -234,7 +234,7 @@
      *
      * Unlike [applyLatestStateful], state accumulation is not stopped with each state change.
      *
-     * ``` kotlin
+     * ```
      *   fun <A> State<Stateful<A>>.applyStatefuls(): State<A> =
      *       changes
      *           .applyStatefuls()
@@ -252,7 +252,7 @@
      * Returns an [Events] that acts like the most recent [Events] to be emitted from the original
      * [Events].
      *
-     * ``` kotlin
+     * ```
      *   fun <A> Events<Events<A>>.flatten() = holdState(emptyEvents).switchEvents()
      * ```
      *
@@ -267,7 +267,7 @@
      * [transform] can perform state accumulation via its [StateScope] receiver. With each
      * invocation of [transform], state accumulation from previous invocation is stopped.
      *
-     * ``` kotlin
+     * ```
      *   fun <A, B> Events<A>.mapLatestStateful(transform: StateScope.(A) -> B): Events<B> =
      *       map { statefully { transform(it) } }.applyLatestStateful()
      * ```
@@ -282,7 +282,7 @@
      * [transform] can perform state accumulation via its [StateScope] receiver. With each
      * invocation of [transform], state accumulation from previous invocation is stopped.
      *
-     * ``` kotlin
+     * ```
      *   fun <A, B> Events<A>.flatMapLatestStateful(
      *       transform: StateScope.(A) -> Events<B>
      *   ): Events<B> =
@@ -495,7 +495,7 @@
      *
      * The optional [numKeys] argument is an optimization used to initialize the internal storage.
      *
-     * ``` kotlin
+     * ```
      *   fun <K, A, B> Events<MapPatch<K, A>>.mapLatestStatefulForKey(
      *       numKeys: Int? = null,
      *       transform: StateScope.(A) -> B,
@@ -516,7 +516,7 @@
      * If the original [Events] is emitting an event at this exact time, then it will be the only
      * even emitted from the result [Events].
      *
-     * ``` kotlin
+     * ```
      *   fun <A> Events<A>.nextOnly(): Events<A> =
      *       EventsLoop<A>().apply {
      *           loopback = map { emptyEvents }.holdState(this@nextOnly).switchEvents()
@@ -535,7 +535,7 @@
     /**
      * Returns an [Events] that skips the next emission of the original [Events].
      *
-     * ``` kotlin
+     * ```
      *   fun <A> Events<A>.skipNext(): Events<A> =
      *       nextOnly().map { this@skipNext }.holdState(emptyEvents).switchEvents()
      * ```
@@ -554,7 +554,7 @@
      * If the original [Events] emits at the same time as [stop], then the returned [Events] will
      * emit that value.
      *
-     * ``` kotlin
+     * ```
      *   fun <A> Events<A>.takeUntil(stop: Events<*>): Events<A> =
      *       stop.map { emptyEvents }.nextOnly().holdState(this).switchEvents()
      * ```
@@ -586,7 +586,7 @@
      * Returns an [Events] that emits values from the original [Events] up to and including a value
      * is emitted that satisfies [predicate].
      *
-     * ``` kotlin
+     * ```
      *   fun <A> Events<A>.takeUntil(predicate: TransactionScope.(A) -> Boolean): Events<A> =
      *       takeUntil(filter(predicate))
      * ```
@@ -602,7 +602,7 @@
      * have been processed; this keeps the value of the [State] consistent during the entire Kairos
      * transaction.
      *
-     * ``` kotlin
+     * ```
      *   fun <A, B> Events<A>.foldState(
      *       initialValue: B,
      *       transform: TransactionScope.(A, B) -> B,
@@ -630,7 +630,7 @@
      * have been processed; this keeps the value of the [State] consistent during the entire Kairos
      * transaction.
      *
-     * ``` kotlin
+     * ```
      *   fun <A, B> Events<A>.foldStateDeferred(
      *       initialValue: DeferredValue<B>,
      *       transform: TransactionScope.(A, B) -> B,
@@ -663,7 +663,7 @@
      * have been processed; this keeps the value of the [State] consistent during the entire Kairos
      * transaction.
      *
-     * ``` kotlin
+     * ```
      *   fun <A> Events<Stateful<A>>.holdLatestStateful(init: Stateful<A>): State<A> {
      *       val (changes, initApplied) = applyLatestStateful(init)
      *       return changes.holdStateDeferred(initApplied)
@@ -724,7 +724,7 @@
      * Returns an [Events] that wraps each emission of the original [Events] into an [IndexedValue],
      * containing the emitted value and its index (starting from zero).
      *
-     * ``` kotlin
+     * ```
      *   fun <A> Events<A>.withIndex(): Events<IndexedValue<A>> {
      *     val index = fold(0) { _, oldIdx -> oldIdx + 1 }
      *     return sample(index) { a, idx -> IndexedValue(idx, a) }
@@ -740,7 +740,7 @@
      * Returns an [Events] containing the results of applying [transform] to each value of the
      * original [Events] and its index (starting from zero).
      *
-     * ``` kotlin
+     * ```
      *   fun <A> Events<A>.mapIndexed(transform: TransactionScope.(Int, A) -> B): Events<B> {
      *       val index = foldState(0) { _, i -> i + 1 }
      *       return sample(index) { a, idx -> transform(idx, a) }
@@ -755,7 +755,7 @@
     /**
      * Returns an [Events] where all subsequent repetitions of the same value are filtered out.
      *
-     * ``` kotlin
+     * ```
      *   fun <A> Events<A>.distinctUntilChanged(): Events<A> {
      *       val state: State<Any?> = holdState(Any())
      *       return filter { it != state.sample() }
@@ -774,7 +774,7 @@
      * Note that the returned [Events] will not emit anything until [other] has emitted at least one
      * value.
      *
-     * ``` kotlin
+     * ```
      *   fun <A, B, C> Events<A>.sample(
      *       other: Events<B>,
      *       transform: TransactionScope.(A, B) -> C,
@@ -796,7 +796,7 @@
      * Returns a [State] that samples the [Transactional] held by the given [State] within the same
      * transaction that the state changes.
      *
-     * ``` kotlin
+     * ```
      *   fun <A> State<Transactional<A>>.sampleTransactionals(): State<A> =
      *       changes
      *           .sampleTransactionals()
@@ -815,7 +815,7 @@
      * Note that this is less efficient than [State.map], which should be preferred if [transform]
      * does not need access to [TransactionScope].
      *
-     * ``` kotlin
+     * ```
      *   fun <A, B> State<A>.mapTransactionally(transform: TransactionScope.(A) -> B): State<B> =
      *       map { transactionally { transform(it) } }.sampleTransactionals()
      * ```
@@ -830,7 +830,7 @@
      * Note that this is less efficient than [combine], which should be preferred if [transform]
      * does not need access to [TransactionScope].
      *
-     * ``` kotlin
+     * ```
      *   fun <A, B, Z> combineTransactionally(
      *       stateA: State<A>,
      *       stateB: State<B>,
@@ -895,7 +895,7 @@
      * Note that this is less efficient than [flatMap], which should be preferred if [transform]
      * does not need access to [TransactionScope].
      *
-     * ``` kotlin
+     * ```
      *   fun <A, B> State<A>.flatMapTransactionally(
      *       transform: TransactionScope.(A) -> State<B>
      *   ): State<B> = map { transactionally { transform(it) } }.sampleTransactionals().flatten()
@@ -950,7 +950,7 @@
      * Returns an [Incremental] that reflects the state of the original [Incremental], but also adds
      * / removes entries based on the state of the original's values.
      *
-     * ``` kotlin
+     * ```
      *   fun <K, V> Incremental<K, State<Maybe<V>>>.applyStateIncrementally(): Incremental<K, V> =
      *       mapValues { (_, v) -> v.changes }
      *           .mergeEventsIncrementallyPromptly()
@@ -971,7 +971,7 @@
      * / removes entries based on the [State] returned from applying [transform] to the original's
      * entries.
      *
-     * ``` kotlin
+     * ```
      *   fun <K, V, U> Incremental<K, V>.mapIncrementalState(
      *       transform: KairosScope.(Map.Entry<K, V>) -> State<Maybe<U>>
      *   ): Incremental<K, U> = mapValues { transform(it) }.applyStateIncrementally()
@@ -986,7 +986,7 @@
      * / removes entries based on the [State] returned from applying [transform] to the original's
      * entries, such that entries are added when that state is `true`, and removed when `false`.
      *
-     * ``` kotlin
+     * ```
      *   fun <K, V> Incremental<K, V>.filterIncrementally(
      *       transform: KairosScope.(Map.Entry<K, V>) -> State<Boolean>
      *   ): Incremental<K, V> = mapIncrementalState { entry ->
@@ -1004,7 +1004,7 @@
      * Returns an [Incremental] that samples the [Transactionals][Transactional] held by the
      * original within the same transaction that the incremental [updates].
      *
-     * ``` kotlin
+     * ```
      *   fun <K, V> Incremental<K, Transactional<V>>.sampleTransactionals(): Incremental<K, V> =
      *       updates
      *           .map { patch -> patch.mapValues { (k, mv) -> mv.map { it.sample() } } }
@@ -1027,7 +1027,7 @@
      * Note that this is less efficient than [mapValues], which should be preferred if [transform]
      * does not need access to [TransactionScope].
      *
-     * ``` kotlin
+     * ```
      *   fun <K, V, U> Incremental<K, V>.mapValuesTransactionally(
      *       transform: TransactionScope.(Map.Entry<K, V>) -> U
      *   ): Incremental<K, U> =
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Transactional.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Transactional.kt
index cf98821..5050511 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Transactional.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Transactional.kt
@@ -51,7 +51,7 @@
  *
  * Useful for recursive definitions.
  *
- * ``` kotlin
+ * ```
  *   fun <A> DeferredValue<Transactional<A>>.defer() = deferredTransactional { get() }
  * ```
  */
@@ -67,7 +67,7 @@
  *
  * Useful for recursive definitions.
  *
- * ``` kotlin
+ * ```
  *   fun <A> Lazy<Transactional<A>>.defer() = deferredTransactional { value }
  * ```
  */
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/store/MapK.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/store/MapK.kt
index e193a49..3dbc6f0 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/store/MapK.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/store/MapK.kt
@@ -21,28 +21,28 @@
  *
  * Let's say you want to write a class that is generic over both a map, and the type of data within
  * the map:
- * ``` kotlin
+ * ```
  *   class Foo<TMap, TKey, TValue> {
  *     val container: TMap<TKey, TElement> // disallowed!
  *   }
  * ```
  *
  * You can use `MapK` to represent the "higher-kinded" type variable `TMap`:
- * ``` kotlin
+ * ```
  *   class Foo<TMap, TKey, TValue> {
  *      val container: MapK<TMap, TKey, TValue> // OK!
  *   }
  * ```
  *
  * Note that Kotlin will not let you use the generic type without parameters as `TMap`:
- * ``` kotlin
+ * ```
  *   val fooHk: MapK<HashMap, Int, String> // not allowed: HashMap requires two type parameters
  * ```
  *
  * To work around this, you need to declare a special type-witness object. This object is only used
  * at compile time and can be stripped out by a minifier because it's never used at runtime.
  *
- * ``` kotlin
+ * ```
  *   class Foo<A, B> : MapK<FooWitness, A, B> { ... }
  *   object FooWitness
  *
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/util/MapPatch.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/util/MapPatch.kt
index 8fe41bc..dde5d82 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/util/MapPatch.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/util/MapPatch.kt
@@ -47,7 +47,7 @@
  * Returns a [MapPatch] that, when applied, includes all of the values from the original [Map].
  *
  * Shorthand for:
- * ``` kotlin
+ * ```
  *   mapValues { (key, value) -> Maybe.present(value) }
  * ```
  */
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/util/Maybe.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/util/Maybe.kt
index 4754bc4..4373705 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/util/Maybe.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/util/Maybe.kt
@@ -69,7 +69,7 @@
  *
  * This can be used instead of Kotlin's built-in nullability (`?.` and `?:`) operators when dealing
  * with complex combinations of nullables:
- * ``` kotlin
+ * ```
  * val aMaybe: Maybe<Any> = ...
  * val bMaybe: Maybe<Any> = ...
  * val result: String = maybe {
diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/Vcn.java b/packages/Vcn/service-b/src/com/android/server/vcn/Vcn.java
index 369ef6a..97f86b1 100644
--- a/packages/Vcn/service-b/src/com/android/server/vcn/Vcn.java
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/Vcn.java
@@ -209,6 +209,8 @@
         this(vcnContext, subscriptionGroup, config, snapshot, vcnCallback, new Dependencies());
     }
 
+    // WARNING: This constructor executes on the binder thread. Thread safety MUST be ensured when
+    // accessing data within this constructor and any methods called from here.
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     public Vcn(
             @NonNull VcnContext vcnContext,
diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/VcnNetworkProvider.java b/packages/Vcn/service-b/src/com/android/server/vcn/VcnNetworkProvider.java
index 99c848f..38fcf09 100644
--- a/packages/Vcn/service-b/src/com/android/server/vcn/VcnNetworkProvider.java
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/VcnNetworkProvider.java
@@ -36,7 +36,6 @@
 import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
-import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
 
@@ -46,6 +45,7 @@
 
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
 
 /**
@@ -56,12 +56,14 @@
  *
  * @hide
  */
+// TODO(b/388919146): Implement a more generic solution to prevent concurrent modifications on
+// mListeners and mRequests
 // TODO(b/374174952): Replace VANILLA_ICE_CREAM with BAKLAVA after Android B finalization
 @TargetApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
 public class VcnNetworkProvider extends NetworkProvider {
     private static final String TAG = VcnNetworkProvider.class.getSimpleName();
 
-    private final Set<NetworkRequestListener> mListeners = new ArraySet<>();
+    private final Set<NetworkRequestListener> mListeners = ConcurrentHashMap.newKeySet();
 
     private final Context mContext;
     private final Handler mHandler;
@@ -72,7 +74,7 @@
      *
      * <p>NetworkRequests are immutable once created, and therefore can be used as stable keys.
      */
-    private final Set<NetworkRequest> mRequests = new ArraySet<>();
+    private final Set<NetworkRequest> mRequests = ConcurrentHashMap.newKeySet();
 
     public VcnNetworkProvider(@NonNull Context context, @NonNull Looper looper) {
         this(context, looper, new Dependencies());
diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
index 6467af4..c8c645f 100644
--- a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
@@ -138,6 +138,8 @@
     @NonNull private final Object mCancellationToken = new Object();
     @NonNull private final PacketLossCalculator mPacketLossCalculator;
 
+    @Nullable private BroadcastReceiver mDeviceIdleReceiver;
+
     @Nullable private IpSecTransformWrapper mInboundTransform;
     @Nullable private IpSecTransformState mLastIpSecTransformState;
 
@@ -168,19 +170,21 @@
         // Register for system broadcasts to monitor idle mode change
         final IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+
+        mDeviceIdleReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(
+                                intent.getAction())
+                        && mPowerManager.isDeviceIdleMode()) {
+                    mLastIpSecTransformState = null;
+                }
+            }
+        };
         getVcnContext()
                 .getContext()
                 .registerReceiver(
-                        new BroadcastReceiver() {
-                            @Override
-                            public void onReceive(Context context, Intent intent) {
-                                if (PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(
-                                                intent.getAction())
-                                        && mPowerManager.isDeviceIdleMode()) {
-                                    mLastIpSecTransformState = null;
-                                }
-                            }
-                        },
+                        mDeviceIdleReceiver,
                         intentFilter,
                         null /* broadcastPermission not required */,
                         mHandler);
@@ -338,7 +342,12 @@
         super.close();
 
         if (mInboundTransform != null) {
-            mInboundTransform.close();
+            mInboundTransform = null;
+        }
+
+        if (mDeviceIdleReceiver != null) {
+            getVcnContext().getContext().unregisterReceiver(mDeviceIdleReceiver);
+            mDeviceIdleReceiver = null;
         }
     }
 
diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkMetricMonitor.java b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
index 1485344..55829a5 100644
--- a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
@@ -165,7 +165,7 @@
         }
     }
 
-    /** Set the IpSecTransform that applied to the Network being monitored */
+    /** Set the IpSecTransform that is applied to the Network being monitored */
     public void setInboundTransform(@NonNull IpSecTransform inTransform) {
         setInboundTransformInternal(new IpSecTransformWrapper(inTransform));
     }
diff --git a/ravenwood/texts/ravenwood-framework-policies.txt b/ravenwood/texts/ravenwood-framework-policies.txt
index 80126df..26b6fe3 100644
--- a/ravenwood/texts/ravenwood-framework-policies.txt
+++ b/ravenwood/texts/ravenwood-framework-policies.txt
@@ -52,7 +52,7 @@
 class android.content.Context keep
     method <init> ()V keep
     method getSystemService (Ljava/lang/Class;)Ljava/lang/Object; keep
-class android.content.pm.PackageManager keep
+class android.content.pm.PackageManager
     method <init> ()V keep
 class android.text.ClipboardManager keep
     method <init> ()V keep
diff --git a/ravenwood/tools/hoststubgen/scripts/dump-jar b/ravenwood/tools/hoststubgen/scripts/dump-jar
index 998357b..02a6d25 100755
--- a/ravenwood/tools/hoststubgen/scripts/dump-jar
+++ b/ravenwood/tools/hoststubgen/scripts/dump-jar
@@ -89,14 +89,33 @@
     # - Some other transient lines
     # - Sometimes the javap shows mysterious warnings, so remove them too.
     #
-    # `/PATTERN-1/,/PATTERN-2/{//!d}` is a trick to delete lines between two patterns, without
-    # the start and the end lines.
+    # Most conversion are simple per-line deletion or simple inline replacement with a regex.
+    #
+    # But removing the constant pool is a bit tricky. It looks like this in the output:
+    #---------------------------
+    #Constant pool:
+    #    #1 = Methodref          #31.#88       // java/lang/Object."<init>":()V
+    #    #2 = Class              #89           // java/lang/UnsupportedOperationException
+    #    :
+    #{ // Or something, I'm not sure if it always ends with a "{".
+    #---------------------------
+    # i.e. we want to delete all lines from "Constant pool:" as long as the first character
+    # is a space.
+    #
+    # If we simply use '/^Constant pool:/,/^[^ ]/d', then it'll delete the "Constant pool:"
+    # line and "{" line too, but again the last line might be important, so we don't want to
+    # delete it.
+    #
+    # So we instead, use '/^Constant pool:/,/^[^ ]/{/^ /d}', which mean:
+    # between lines matching '/^Constant pool:/' and '/^[^ ]/', delete lines that start with
+    # a space. (=='/^ /d').
+    #
     sed -e 's/#[0-9][0-9]*/#x/g' \
         -e 's/^\( *\)[0-9][0-9]*:/\1x:/' \
-        -e '/^Constant pool:/,/^[^ ]/{//!d}' \
+        -e '/^Constant pool:/,/^[^ ]/{/^ /d}' \
         -e '/^ *line *[0-9][0-9]*: *[0-9][0-9]*$/d' \
-        -e '/SHA-256 checksum/d' \
-        -e '/Last modified/d' \
+        -e '/^ *SHA-256 checksum/d' \
+        -e '/^ *Last modified/d' \
         -e '/^Classfile jar/d' \
         -e '/\[warning\]/d'
   else
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
index be1b6ca..c550083 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
@@ -413,8 +413,8 @@
     }
 
     private fun parseClass(fields: Array<String>) {
-        if (fields.size < 3) {
-            throw ParseException("Class ('c') expects 2 fields.")
+        if (fields.size <= 1) {
+            throw ParseException("Class ('c') expects 1 or 2 fields.")
         }
         val className = fields[1]
 
@@ -424,7 +424,9 @@
         // :aidl, etc?
         val classType = resolveSpecialClass(className)
 
-        if (fields[2].startsWith("!")) {
+        val policyStr = if (fields.size > 2) { fields[2] } else { "" }
+
+        if (policyStr.startsWith("!")) {
             if (classType != SpecialClass.NotSpecial) {
                 // We could support it, but not needed at least for now.
                 throw ParseException(
@@ -432,10 +434,10 @@
                 )
             }
             // It's a redirection class.
-            val toClass = fields[2].substring(1)
+            val toClass = policyStr.substring(1)
 
             processor.onRedirectionClass(className, toClass)
-        } else if (fields[2].startsWith("~")) {
+        } else if (policyStr.startsWith("~")) {
             if (classType != SpecialClass.NotSpecial) {
                 // We could support it, but not needed at least for now.
                 throw ParseException(
@@ -443,11 +445,23 @@
                 )
             }
             // It's a class-load hook
-            val callback = fields[2].substring(1)
+            val callback = policyStr.substring(1)
 
             processor.onClassLoadHook(className, callback)
         } else {
-            val policy = parsePolicy(fields[2])
+            // Special case: if it's a class directive with no policy, then it encloses
+            // members, but we don't apply any policy to the class itself.
+            // This is only allowed in a not-special case.
+            if (policyStr == "") {
+                if (classType == SpecialClass.NotSpecial && superClass == null) {
+                    currentClassName = className
+                    processor.onSimpleClassStart(className)
+                    return
+                }
+                throw ParseException("Special class or subclass directive must have a policy")
+            }
+
+            val policy = parsePolicy(policyStr)
             if (!policy.isUsableWithClasses) {
                 throw ParseException("Class can't have policy '$policy'")
             }
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/diff-and-update-golden.sh b/ravenwood/tools/hoststubgen/test-tiny-framework/diff-and-update-golden.sh
index 8408a18..23699fd 100755
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/diff-and-update-golden.sh
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/diff-and-update-golden.sh
@@ -37,8 +37,7 @@
 GOLDEN_DIR=${GOLDEN_DIR:-golden-output}
 mkdir -p $GOLDEN_DIR
 
-# TODO(b/388562869) We shouldn't need `--ignore-matching-lines`, but the golden files may not have the "Constant pool:" lines.
-DIFF_CMD=${DIFF_CMD:-diff -u --ignore-blank-lines --ignore-space-change --ignore-matching-lines='^\(Constant.pool:\|{\)$'}
+DIFF_CMD=${DIFF_CMD:-./tiny-framework-dump-test.py run-diff}
 
 update=0
 three_way=0
@@ -63,12 +62,10 @@
 shift $(($OPTIND - 1))
 
 # Build the dump files, which are the input of this test.
-run ${BUILD_CMD:=m} dump-jar tiny-framework-dump-test
-
+run ${BUILD_CMD:-m} dump-jar tiny-framework-dump-test
 
 # Get the path to the generate text files. (not the golden files.)
 # We get them from $OUT/module-info.json
-
 files=(
 $(python3 -c '
 import sys
@@ -78,7 +75,7 @@
 with open(sys.argv[1], "r") as f:
     data = json.load(f)
 
-    # Equivalent to: jq -r '.["tiny-framework-dump-test"]["installed"][]'
+    # Equivalent to:    jq -r '.["tiny-framework-dump-test"]["installed"][]'
     for path in data["tiny-framework-dump-test"]["installed"]:
 
       if "golden-output" in path:
diff --git a/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py
index c35d6d1..7617482 100755
--- a/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py
+++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py
@@ -28,9 +28,16 @@
 
 # Run diff.
 def run_diff(file1, file2):
-    # TODO(b/388562869) We shouldn't need `--ignore-matching-lines`, but the golden files may not have the "Constant pool:" lines.
-    command = ['diff', '-u', '--ignore-blank-lines',
+    command = ['diff', '-u',
+               '--ignore-blank-lines',
                '--ignore-space-change',
+
+               # Ignore the class file version.
+               '--ignore-matching-lines=^ *\(major\|minor\) version:$',
+
+               # We shouldn't need `--ignore-matching-lines`, but somehow
+               # the golden files were generated without these lines for b/388562869,
+               # so let's just ignore them.
                '--ignore-matching-lines=^\(Constant.pool:\|{\)$',
                file1, file2]
     print(' '.join(command))
@@ -85,4 +92,13 @@
 
 
 if __name__ == "__main__":
+    args = sys.argv
+
+    # This script is used by diff-and-update-golden.sh too.
+    if len(args) > 1 and args[1] == "run-diff":
+        if run_diff(args[2], args[3]):
+            sys.exit(0)
+        else:
+            sys.exit(1)
+
     unittest.main(verbosity=2)
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 8e037c3..7275881 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1841,9 +1841,6 @@
     public void notifyQuickSettingsTilesChanged(
             @UserIdInt int userId, @NonNull List<ComponentName> tileComponentNames) {
         notifyQuickSettingsTilesChanged_enforcePermission();
-        if (!android.view.accessibility.Flags.a11yQsShortcut()) {
-            return;
-        }
         if (DEBUG) {
             Slog.d(LOG_TAG, TextUtils.formatSimple(
                     "notifyQuickSettingsTilesChanged userId: %d, tileComponentNames: %s",
@@ -2278,9 +2275,6 @@
     private void restoreShortcutTargets(
             String newValue, @UserShortcutType int shortcutType, int userId) {
         assertNoTapShortcut(shortcutType);
-        if (shortcutType == QUICK_SETTINGS && !android.view.accessibility.Flags.a11yQsShortcut()) {
-            return;
-        }
 
         synchronized (mLock) {
             final AccessibilityUserState userState = getUserStateLocked(userId);
@@ -3894,9 +3888,6 @@
      */
     private void updateAccessibilityShortcutTargetsLocked(
             AccessibilityUserState userState, @UserShortcutType int shortcutType) {
-        if (shortcutType == QUICK_SETTINGS && !android.view.accessibility.Flags.a11yQsShortcut()) {
-            return;
-        }
         if (shortcutType == SOFTWARE) {
             // Update accessibility button availability.
             for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) {
@@ -4079,9 +4070,7 @@
         final List<Integer> shortcutTypes = new ArrayList<>(4);
         shortcutTypes.add(HARDWARE);
         shortcutTypes.add(SOFTWARE);
-        if (android.view.accessibility.Flags.a11yQsShortcut()) {
-            shortcutTypes.add(QUICK_SETTINGS);
-        }
+        shortcutTypes.add(QUICK_SETTINGS);
         if (android.provider.Flags.a11yStandaloneGestureEnabled()) {
             shortcutTypes.add(GESTURE);
         }
@@ -4385,13 +4374,7 @@
 
     private void launchAccessibilityFrameworkFeature(int displayId, ComponentName assignedTarget) {
         if (assignedTarget.equals(ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME)) {
-            //import com.android.systemui.Flags;
-            if (com.android.systemui.Flags.hearingAidsQsTileDialog()) {
-                launchHearingDevicesDialog();
-            } else {
-                launchAccessibilitySubSettings(displayId,
-                        ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME);
-            }
+            launchHearingDevicesDialog();
         }
     }
 
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 1dba629b..cffdfbd 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -5666,13 +5666,6 @@
         }
 
         public boolean canAccessAppWidget(Widget widget, int uid, String packageName) {
-            if (isDifferentPackageFromHost(widget.host, packageName)
-                    && isDifferentPackageFromProvider(widget.provider, packageName)) {
-                // Apps providing AppWidget are only allowed to access widgets provided by the
-                // same package. Similarly, apps hosting AppWidget are only allowed to access
-                // widgets hosted by the same package.
-                return false;
-            }
             if (isHostInPackageForUid(widget.host, uid, packageName)) {
                 // Apps hosting the AppWidget have access to it.
                 return true;
@@ -5775,19 +5768,6 @@
                     && provider.id.componentName.getPackageName().equals(packageName);
         }
 
-        private boolean isDifferentPackageFromHost(
-                @Nullable final Host host, @Nullable final String packageName) {
-            return packageName == null || host == null || host.id == null
-                || !packageName.equals(host.id.packageName);
-        }
-
-        private boolean isDifferentPackageFromProvider(
-                @Nullable final Provider provider, @Nullable final String packageName) {
-            return packageName == null || provider == null || provider.id == null
-                    || provider.id.componentName == null
-                    || !packageName.equals(provider.id.componentName.getPackageName());
-        }
-
         private boolean isProfileEnabled(int profileId) {
             final long identity = Binder.clearCallingIdentity();
             try {
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 260ea75..5edd9d7 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -21,7 +21,6 @@
 import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_ENABLED;
 import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY;
 import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY;
-import static android.companion.virtual.VirtualDeviceParams.ACTIVITY_POLICY_DEFAULT_ALLOWED;
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
 import static android.companion.virtual.VirtualDeviceParams.NAVIGATION_POLICY_DEFAULT_ALLOWED;
@@ -492,17 +491,10 @@
                     this, getDeviceId(), getPersistentDeviceId(), mParams.getName());
         }
 
-        if (Flags.dynamicPolicy()) {
-            mActivityPolicyExemptions = new ArraySet<>(
-                    mParams.getDevicePolicy(POLICY_TYPE_ACTIVITY) == DEVICE_POLICY_DEFAULT
-                            ? mParams.getBlockedActivities()
-                            : mParams.getAllowedActivities());
-        } else {
-            mActivityPolicyExemptions =
-                    mParams.getDefaultActivityPolicy() == ACTIVITY_POLICY_DEFAULT_ALLOWED
-                            ? mParams.getBlockedActivities()
-                            : mParams.getAllowedActivities();
-        }
+        mActivityPolicyExemptions = new ArraySet<>(
+                mParams.getDevicePolicy(POLICY_TYPE_ACTIVITY) == DEVICE_POLICY_DEFAULT
+                        ? mParams.getBlockedActivities()
+                        : mParams.getAllowedActivities());
 
         if (Flags.vdmCustomIme() && mParams.getInputMethodComponent() != null) {
             final String imeId = mParams.getInputMethodComponent().flattenToShortString();
@@ -587,12 +579,8 @@
     @Override  // Binder call
     public @VirtualDeviceParams.DevicePolicy int getDevicePolicy(
             @VirtualDeviceParams.PolicyType int policyType) {
-        if (Flags.dynamicPolicy()) {
-            synchronized (mVirtualDeviceLock) {
-                return mDevicePolicies.get(policyType, DEVICE_POLICY_DEFAULT);
-            }
-        } else {
-            return mParams.getDevicePolicy(policyType);
+        synchronized (mVirtualDeviceLock) {
+            return mDevicePolicies.get(policyType, DEVICE_POLICY_DEFAULT);
         }
     }
 
@@ -891,9 +879,6 @@
     public void setDevicePolicy(@VirtualDeviceParams.DynamicPolicyType int policyType,
             @VirtualDeviceParams.DevicePolicy int devicePolicy) {
         checkCallerIsDeviceOwner();
-        if (!Flags.dynamicPolicy()) {
-            return;
-        }
 
         switch (policyType) {
             case POLICY_TYPE_RECENTS:
@@ -924,23 +909,21 @@
                 }
                 break;
             case POLICY_TYPE_CLIPBOARD:
-                if (Flags.crossDeviceClipboard()) {
-                    if (devicePolicy == DEVICE_POLICY_CUSTOM
+                if (devicePolicy == DEVICE_POLICY_CUSTOM
                             && mContext.checkCallingOrSelfPermission(ADD_TRUSTED_DISPLAY)
                             != PackageManager.PERMISSION_GRANTED) {
-                        throw new SecurityException("Requires ADD_TRUSTED_DISPLAY permission to "
-                                + "set a custom clipboard policy.");
-                    }
-                    synchronized (mVirtualDeviceLock) {
-                        for (int i = 0; i < mVirtualDisplays.size(); i++) {
-                            VirtualDisplayWrapper wrapper = mVirtualDisplays.valueAt(i);
-                            if (!wrapper.isTrusted() && !wrapper.isMirror()) {
-                                throw new SecurityException("All displays must be trusted for "
-                                        + "devices with custom clipboard policy.");
-                            }
+                    throw new SecurityException("Requires ADD_TRUSTED_DISPLAY permission to "
+                            + "set a custom clipboard policy.");
+                }
+                synchronized (mVirtualDeviceLock) {
+                    for (int i = 0; i < mVirtualDisplays.size(); i++) {
+                        VirtualDisplayWrapper wrapper = mVirtualDisplays.valueAt(i);
+                        if (!wrapper.isTrusted() && !wrapper.isMirror()) {
+                            throw new SecurityException("All displays must be trusted for "
+                                    + "devices with custom clipboard policy.");
                         }
-                        mDevicePolicies.put(policyType, devicePolicy);
                     }
+                    mDevicePolicies.put(policyType, devicePolicy);
                 }
                 break;
             case POLICY_TYPE_BLOCKED_ACTIVITY:
@@ -1443,15 +1426,11 @@
     private GenericWindowPolicyController createWindowPolicyControllerLocked(
             @NonNull Set<String> displayCategories) {
         final boolean activityLaunchAllowedByDefault =
-                Flags.dynamicPolicy()
-                    ? getDevicePolicy(POLICY_TYPE_ACTIVITY) == DEVICE_POLICY_DEFAULT
-                    : mParams.getDefaultActivityPolicy() == ACTIVITY_POLICY_DEFAULT_ALLOWED;
+                getDevicePolicy(POLICY_TYPE_ACTIVITY) == DEVICE_POLICY_DEFAULT;
         final boolean crossTaskNavigationAllowedByDefault =
                 mParams.getDefaultNavigationPolicy() == NAVIGATION_POLICY_DEFAULT_ALLOWED;
         final boolean showTasksInHostDeviceRecents =
                 getDevicePolicy(POLICY_TYPE_RECENTS) == DEVICE_POLICY_DEFAULT;
-        final ComponentName homeComponent =
-                Flags.vdmCustomHome() ? mParams.getHomeComponent() : null;
 
         if (mActivityListenerAdapter == null) {
             mActivityListenerAdapter = new GwpcActivityListener();
@@ -1472,7 +1451,7 @@
                 mActivityListenerAdapter,
                 displayCategories,
                 showTasksInHostDeviceRecents,
-                homeComponent);
+                mParams.getHomeComponent());
         gwpc.registerRunningAppsChangedListener(/* listener= */ this);
         return gwpc;
     }
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 40726b4..a60fa69 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -216,17 +216,14 @@
                 VIRTUAL_DEVICE_SERVICE_ORDERED_ID,
                 mActivityInterceptorCallback);
 
-        if (Flags.persistentDeviceIdApi()) {
-            CompanionDeviceManager cdm =
-                    getContext().getSystemService(CompanionDeviceManager.class);
-            if (cdm != null) {
-                onCdmAssociationsChanged(cdm.getAllAssociations(UserHandle.USER_ALL));
-                cdm.addOnAssociationsChangedListener(getContext().getMainExecutor(),
-                        this::onCdmAssociationsChanged, UserHandle.USER_ALL);
-            } else {
-                Slog.e(TAG, "Failed to find CompanionDeviceManager. No CDM association info "
-                        + " will be available.");
-            }
+        CompanionDeviceManager cdm = getContext().getSystemService(CompanionDeviceManager.class);
+        if (cdm != null) {
+            onCdmAssociationsChanged(cdm.getAllAssociations(UserHandle.USER_ALL));
+            cdm.addOnAssociationsChangedListener(getContext().getMainExecutor(),
+                    this::onCdmAssociationsChanged, UserHandle.USER_ALL);
+        } else {
+            Slog.e(TAG, "Failed to find CompanionDeviceManager. No CDM association info "
+                    + " will be available.");
         }
         if (android.companion.virtualdevice.flags.Flags.deviceAwareDisplayPower()) {
             mStrongAuthTracker = new StrongAuthTracker(getContext());
@@ -338,14 +335,6 @@
         final long identity = Binder.clearCallingIdentity();
         try {
             getContext().sendBroadcastAsUser(i, UserHandle.ALL);
-
-            if (!Flags.persistentDeviceIdApi()) {
-                synchronized (mVirtualDeviceManagerLock) {
-                    if (mVirtualDevices.size() == 0) {
-                        unregisterCdmAssociationListener();
-                    }
-                }
-            }
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -379,21 +368,6 @@
         }
     }
 
-    @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES)
-    private void registerCdmAssociationListener() {
-        final CompanionDeviceManager cdm = getContext().getSystemService(
-                CompanionDeviceManager.class);
-        cdm.addOnAssociationsChangedListener(getContext().getMainExecutor(),
-                mCdmAssociationListener);
-    }
-
-    @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES)
-    private void unregisterCdmAssociationListener() {
-        final CompanionDeviceManager cdm = getContext().getSystemService(
-                CompanionDeviceManager.class);
-        cdm.removeOnAssociationsChangedListener(mCdmAssociationListener);
-    }
-
     void onCdmAssociationsChanged(List<AssociationInfo> associations) {
         ArrayMap<String, AssociationInfo> vdmAssociations = new ArrayMap<>();
         for (int i = 0; i < associations.size(); ++i) {
@@ -479,9 +453,8 @@
             AssociationInfo associationInfo = getAssociationInfo(packageName, associationId);
             if (associationInfo == null) {
                 throw new IllegalArgumentException("No association with ID " + associationId);
-            } else if (!VIRTUAL_DEVICE_COMPANION_DEVICE_PROFILES
-                    .contains(associationInfo.getDeviceProfile())
-                    && Flags.persistentDeviceIdApi()) {
+            } else if (!VIRTUAL_DEVICE_COMPANION_DEVICE_PROFILES.contains(
+                    associationInfo.getDeviceProfile())) {
                 throw new IllegalArgumentException("Unsupported CDM Association device profile "
                         + associationInfo.getDeviceProfile() + " for virtual device creation.");
             }
@@ -522,14 +495,6 @@
             Counter.logIncrement("virtual_devices.value_virtual_devices_created_count");
 
             synchronized (mVirtualDeviceManagerLock) {
-                if (!Flags.persistentDeviceIdApi() && mVirtualDevices.size() == 0) {
-                    final long callingId = Binder.clearCallingIdentity();
-                    try {
-                        registerCdmAssociationListener();
-                    } finally {
-                        Binder.restoreCallingIdentity(callingId);
-                    }
-                }
                 mVirtualDevices.put(deviceId, virtualDevice);
             }
 
diff --git a/services/core/Android.bp b/services/core/Android.bp
index d6bffcb..42385fc 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -37,6 +37,7 @@
         ":framework_native_aidl",
         ":gsiservice_aidl",
         ":installd_aidl",
+        ":mmd_aidl",
         ":storaged_aidl",
         ":vold_aidl",
     ],
@@ -246,6 +247,7 @@
         "aconfig_new_storage_flags_lib",
         "powerstats_flags_lib",
         "locksettings_flags_lib",
+        "mmd_flags_lib",
         "profiling_flags_lib",
         "android.adpf.sessionmanager_aidl-java",
         "uprobestats_flags_java_lib",
diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java
index 6459016..87222a6 100644
--- a/services/core/java/com/android/server/GestureLauncherService.java
+++ b/services/core/java/com/android/server/GestureLauncherService.java
@@ -564,7 +564,8 @@
         return Settings.Secure.getIntForUser(
                 context.getContentResolver(),
                 Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE,
-                LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER,
+                context.getResources().getInteger(
+                        R.integer.config_doubleTapPowerGestureMultiTargetDefaultAction),
                 userId);
     }
 
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index 6858e29..ef769cf 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -9,6 +9,7 @@
 
 # Zram writeback
 per-file ZramWriteback.java = minchan@google.com, rajekumar@google.com
+per-file ZramMaintenance.java = kawasin@google.com
 
 # ServiceWatcher
 per-file ServiceWatcher.java = sooniln@google.com
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index b7bc4e4..19e7e06 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -41,6 +41,7 @@
 import static android.os.storage.OnObbStateChangeListener.ERROR_PERMISSION_DENIED;
 import static android.os.storage.OnObbStateChangeListener.MOUNTED;
 import static android.os.storage.OnObbStateChangeListener.UNMOUNTED;
+import static android.mmd.flags.Flags.mmdEnabled;
 
 import static com.android.internal.util.XmlUtils.readStringAttribute;
 import static com.android.internal.util.XmlUtils.writeStringAttribute;
@@ -945,12 +946,17 @@
             });
         refreshZramSettings();
 
-        // Schedule zram writeback unless zram is disabled by persist.sys.zram_enabled
-        String zramPropValue = SystemProperties.get(ZRAM_ENABLED_PROPERTY);
-        if (!zramPropValue.equals("0")
-                && mContext.getResources().getBoolean(
+        if (mmdEnabled()) {
+            // TODO: b/375432472 - Start zram maintenance only when zram is enabled.
+            ZramMaintenance.startZramMaintenance(mContext);
+        } else {
+            // Schedule zram writeback unless zram is disabled by persist.sys.zram_enabled
+            String zramPropValue = SystemProperties.get(ZRAM_ENABLED_PROPERTY);
+            if (!zramPropValue.equals("0")
+                    && mContext.getResources().getBoolean(
                     com.android.internal.R.bool.config_zramWriteback)) {
-            ZramWriteback.scheduleZramWriteback(mContext);
+                ZramWriteback.scheduleZramWriteback(mContext);
+            }
         }
 
         configureTranscoding();
@@ -977,7 +983,7 @@
             // sole writer.
             SystemProperties.set(ZRAM_ENABLED_PROPERTY, desiredPropertyValue);
             // Schedule writeback only if zram is being enabled.
-            if (desiredPropertyValue.equals("1")
+            if (!mmdEnabled() && desiredPropertyValue.equals("1")
                     && mContext.getResources().getBoolean(
                         com.android.internal.R.bool.config_zramWriteback)) {
                 ZramWriteback.scheduleZramWriteback(mContext);
diff --git a/services/core/java/com/android/server/ZramMaintenance.java b/services/core/java/com/android/server/ZramMaintenance.java
new file mode 100644
index 0000000..cdb4812
--- /dev/null
+++ b/services/core/java/com/android/server/ZramMaintenance.java
@@ -0,0 +1,118 @@
+/*
+ * 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;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.IBinder;
+import android.os.IMmd;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.util.Slog;
+
+import java.time.Duration;
+
+/**
+ * Schedules zram maintenance (e.g. zram writeback, zram recompression).
+ *
+ * <p>ZramMaintenance notifies mmd the good timing to execute zram maintenance based on:
+ *
+ * <ul>
+ * <li>Enough interval has passed.
+ * <li>The system is idle.
+ * <li>The battery is not low.
+ * </ul>
+ */
+public class ZramMaintenance extends JobService {
+    private static final String TAG = ZramMaintenance.class.getName();
+    // Job id must be unique across all clients of the same uid. ZramMaintenance uses the bug number
+    // as the job id.
+    private static final int JOB_ID = 375432472;
+    private static final ComponentName sZramMaintenance =
+            new ComponentName("android", ZramMaintenance.class.getName());
+
+    private static final String FIRST_DELAY_SECONDS_PROP =
+            "mm.zram.maintenance.first_delay_seconds";
+    // The default is 1 hour.
+    private static final long DEFAULT_FIRST_DELAY_SECONDS = 3600;
+    private static final String PERIODIC_DELAY_SECONDS_PROP =
+            "mm.zram.maintenance.periodic_delay_seconds";
+    // The default is 1 hour.
+    private static final long DEFAULT_PERIODIC_DELAY_SECONDS = 3600;
+    private static final String REQUIRE_DEVICE_IDLE_PROP =
+            "mm.zram.maintenance.require_device_idle";
+    private static final boolean DEFAULT_REQUIRE_DEVICE_IDLE =
+            true;
+    private static final String REQUIRE_BATTERY_NOT_LOW_PROP =
+            "mm.zram.maintenance.require_battry_not_low";
+    private static final boolean DEFAULT_REQUIRE_BATTERY_NOT_LOW =
+            true;
+
+    @Override
+    public boolean onStartJob(JobParameters params) {
+        IBinder binder = ServiceManager.getService("mmd");
+        if (binder != null) {
+            IMmd mmd = IMmd.Stub.asInterface(binder);
+            try {
+                mmd.doZramMaintenance();
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to doZramMaintenance", e);
+            }
+        } else {
+            Slog.w(TAG, "binder not found");
+        }
+        Duration delay = Duration.ofSeconds(SystemProperties.getLong(PERIODIC_DELAY_SECONDS_PROP,
+                DEFAULT_PERIODIC_DELAY_SECONDS));
+        scheduleZramMaintenance(this, delay);
+        return true;
+    }
+
+    @Override
+    public boolean onStopJob(JobParameters params) {
+        return false;
+    }
+
+    /**
+     * Starts periodical zram maintenance.
+     */
+    public static void startZramMaintenance(Context context) {
+        Duration delay = Duration.ofSeconds(
+                SystemProperties.getLong(FIRST_DELAY_SECONDS_PROP, DEFAULT_FIRST_DELAY_SECONDS));
+        scheduleZramMaintenance(context, delay);
+    }
+
+    private static void scheduleZramMaintenance(Context context, Duration delay) {
+        JobScheduler js = context.getSystemService(JobScheduler.class);
+
+        if (js != null) {
+            js.schedule(new JobInfo.Builder(JOB_ID, sZramMaintenance)
+                    .setMinimumLatency(delay.toMillis())
+                    .setRequiresDeviceIdle(
+                            SystemProperties.getBoolean(REQUIRE_DEVICE_IDLE_PROP,
+                                    DEFAULT_REQUIRE_DEVICE_IDLE))
+                    .setRequiresBatteryNotLow(
+                            SystemProperties.getBoolean(REQUIRE_BATTERY_NOT_LOW_PROP,
+                                    DEFAULT_REQUIRE_BATTERY_NOT_LOW))
+                    .build());
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 4b8770b..50876da 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -277,7 +277,9 @@
     private static final long DEFAULT_FOLLOW_UP_OOMADJ_UPDATE_WAIT_DURATION = 1000L;
 
     /** The default value to {@link #KEY_FREEZER_CUTOFF_ADJ} */
-    private static final int DEFAULT_FREEZER_CUTOFF_ADJ = ProcessList.CACHED_APP_MIN_ADJ;
+    private static final int DEFAULT_FREEZER_CUTOFF_ADJ =
+            Flags.prototypeAggressiveFreezing() ? ProcessList.HOME_APP_ADJ
+                    : ProcessList.CACHED_APP_MIN_ADJ;
 
     /**
      * Same as {@link TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED}
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index c99e8c8..d3a5254 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -155,6 +155,7 @@
         "android_core_networking",
         "android_health_services",
         "android_sdk",
+        "android_kernel",
         "aoc",
         "app_widgets",
         "arc_next",
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index cfcede8..d9be471 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -270,6 +270,16 @@
 }
 
 flag {
+    name: "prototype_aggressive_freezing"
+    namespace: "backstage_power"
+    description: "Grant PROCESS_CAPABILITY_CPU_TIME to as many valid states and aggressively freeze other states"
+    bug: "370798593"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "add_modify_raw_oom_adj_service_level"
     namespace: "backstage_power"
     description: "Add a SERVICE_ADJ level to the modifyRawOomAdj method"
diff --git a/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java b/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java
index e4c36cc..695032e 100644
--- a/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java
+++ b/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java
@@ -110,7 +110,12 @@
             }
             db.setTransactionSuccessful();
         } finally {
-            db.endTransaction();
+            try {
+                db.endTransaction();
+            } catch (SQLiteException exception) {
+                Slog.e(LOG_TAG, "Couldn't commit transaction when inserting discrete ops, database"
+                        + " file size (bytes) : " + getDatabaseFile().length(), exception);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/crashrecovery/OWNERS b/services/core/java/com/android/server/crashrecovery/OWNERS
index daa0211..02df986 100644
--- a/services/core/java/com/android/server/crashrecovery/OWNERS
+++ b/services/core/java/com/android/server/crashrecovery/OWNERS
@@ -1,3 +1,2 @@
-ancr@google.com
 harshitmahajan@google.com
 robertogil@google.com
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index b530da2..ca001b9c 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -4188,6 +4188,9 @@
             }
         }
 
+        /**
+         * This method must not be called with mCallback held or deadlock will ensue.
+         */
         @Override
         public void binderDied() {
             synchronized (mCallback) {
@@ -4248,17 +4251,8 @@
                 }
             }
 
-            return transmitDisplayEvent(displayId, event);
-        }
-
-        /**
-         * Transmit a single display event.  The client is presumed ready.  Return true on success
-         * and false if the client died.
-         */
-        private boolean transmitDisplayEvent(int displayId, @DisplayEvent int event) {
-            // The client is ready to receive the event.
             try {
-                mCallback.onDisplayEvent(displayId, event);
+                transmitDisplayEvent(displayId, event);
                 return true;
             } catch (RemoteException ex) {
                 Slog.w(TAG, "Failed to notify process "
@@ -4269,6 +4263,18 @@
         }
 
         /**
+         * Transmit a single display event.  The client is presumed ready.  This throws if the
+         * client has died; callers must catch and handle the exception.  The exception cannot be
+         * handled directly here because {@link #binderDied()} must not be called whilst holding
+         * the mCallback lock.
+         */
+        private void transmitDisplayEvent(int displayId, @DisplayEvent int event)
+                throws RemoteException {
+            // The client is ready to receive the event.
+            mCallback.onDisplayEvent(displayId, event);
+        }
+
+        /**
          * Return true if the client is interested in this event.
          */
         private boolean shouldSendDisplayEvent(@DisplayEvent int event) {
@@ -4376,27 +4382,32 @@
         // would be unusual to do so.  The method returns true on success.
         // This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
         public boolean dispatchPending() {
-            synchronized (mCallback) {
-                if (mPendingEvents == null || mPendingEvents.isEmpty() || !mAlive) {
+            try {
+                synchronized (mCallback) {
+                    if (mPendingEvents == null || mPendingEvents.isEmpty() || !mAlive) {
+                        return true;
+                    }
+                    if (!isReadyLocked()) {
+                        return false;
+                    }
+                    for (int i = 0; i < mPendingEvents.size(); i++) {
+                        Event displayEvent = mPendingEvents.get(i);
+                        if (DEBUG) {
+                            Slog.d(TAG, "Send pending display event #" + i + " "
+                                    + displayEvent.displayId + "/"
+                                    + displayEvent.event + " to " + mUid + "/" + mPid);
+                        }
+                        transmitDisplayEvent(displayEvent.displayId, displayEvent.event);
+                    }
+                    mPendingEvents.clear();
                     return true;
                 }
-                if (!isReadyLocked()) {
-                    return false;
-                }
-                for (int i = 0; i < mPendingEvents.size(); i++) {
-                    Event displayEvent = mPendingEvents.get(i);
-                    if (DEBUG) {
-                        Slog.d(TAG, "Send pending display event #" + i + " "
-                                + displayEvent.displayId + "/"
-                                + displayEvent.event + " to " + mUid + "/" + mPid);
-                    }
-                    if (!transmitDisplayEvent(displayEvent.displayId, displayEvent.event)) {
-                        Slog.d(TAG, "Drop pending events for dead process " + mPid);
-                        break;
-                    }
-                }
-                mPendingEvents.clear();
-                return true;
+            } catch (RemoteException ex) {
+                Slog.w(TAG, "Failed to notify process "
+                        + mPid + " that display topology changed, assuming it died.", ex);
+                binderDied();
+                return false;
+
             }
         }
 
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 4e57d67..43aa6f4 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -258,6 +258,11 @@
             Flags::subscribeGranularDisplayEvents
     );
 
+    private final FlagState mBaseDensityForExternalDisplays = new FlagState(
+            Flags.FLAG_BASE_DENSITY_FOR_EXTERNAL_DISPLAYS,
+            Flags::baseDensityForExternalDisplays
+    );
+
     /**
      * @return {@code true} if 'port' is allowed in display layout configuration file.
      */
@@ -553,6 +558,14 @@
     }
 
     /**
+     * @return {@code true} if the flag for base density for external displays is enabled
+     */
+    public boolean isBaseDensityForExternalDisplaysEnabled() {
+        return mBaseDensityForExternalDisplays.isEnabled();
+    }
+
+
+    /**
      * dumps all flagstates
      * @param pw printWriter
      */
@@ -606,6 +619,7 @@
         pw.println(" " + mDisplayListenerPerformanceImprovementsFlagState);
         pw.println(" " + mSubscribeGranularDisplayEvents);
         pw.println(" " + mEnableDisplayContentModeManagementFlagState);
+        pw.println(" " + mBaseDensityForExternalDisplays);
     }
 
     private static class FlagState {
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index afae07c..00a9dcb 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
@@ -471,3 +471,11 @@
       purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "base_density_for_external_displays"
+    namespace: "lse_desktop_experience"
+    description: "Feature flag for setting a base density for external displays."
+    bug: "382954433"
+    is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/hdmi/PowerStatusMonitorActionFromPlayback.java b/services/core/java/com/android/server/hdmi/PowerStatusMonitorActionFromPlayback.java
index d05ded5..a2465d1 100644
--- a/services/core/java/com/android/server/hdmi/PowerStatusMonitorActionFromPlayback.java
+++ b/services/core/java/com/android/server/hdmi/PowerStatusMonitorActionFromPlayback.java
@@ -28,21 +28,17 @@
  */
 public class PowerStatusMonitorActionFromPlayback extends HdmiCecFeatureAction {
     private static final String TAG = "PowerStatusMonitorActionFromPlayback";
+    // State that waits for next monitoring.
+    private static final int STATE_WAIT_FOR_NEXT_MONITORING = 1;
 
     // State that waits for <Report Power Status> once sending <Give Device Power Status>
-    // to all external devices.
-    private static final int STATE_WAIT_FOR_REPORT_POWER_STATUS = 1;
-    // State that waits for next monitoring.
-    private static final int STATE_WAIT_FOR_NEXT_MONITORING = 2;
+    // to the TV.
+    private static final int STATE_WAIT_FOR_REPORT_POWER_STATUS = 2;
+
     // Monitoring interval (60s)
     @VisibleForTesting
     protected static final int MONITORING_INTERVAL_MS = 60000;
     // Timeout once sending <Give Device Power Status>
-    private static final int REPORT_POWER_STATUS_TIMEOUT_MS = 5000;
-    // Maximum number of retries in case the <Give Device Power Status> failed being sent or times
-    // out.
-    private static final int GIVE_POWER_STATUS_FOR_SOURCE_RETRIES = 5;
-    private int mPowerStatusRetries = 0;
 
     PowerStatusMonitorActionFromPlayback(HdmiCecLocalDevice source) {
         super(source);
@@ -68,11 +64,10 @@
 
     private boolean handleReportPowerStatusFromTv(HdmiCecMessage cmd) {
         int powerStatus = cmd.getParams()[0] & 0xFF;
-        mState = STATE_WAIT_FOR_NEXT_MONITORING;
-        addTimer(mState, MONITORING_INTERVAL_MS);
         if (powerStatus == POWER_STATUS_STANDBY) {
             Slog.d(TAG, "TV reported it turned off, going to sleep.");
             source().getService().standby();
+            finish();
             return true;
         }
         return false;
@@ -80,34 +75,28 @@
 
     @Override
     void handleTimerEvent(int state) {
+        if (mState != state) {
+            return;
+        }
+
         switch (mState) {
             case STATE_WAIT_FOR_NEXT_MONITORING:
-                mPowerStatusRetries = 0;
                 queryPowerStatus();
                 break;
             case STATE_WAIT_FOR_REPORT_POWER_STATUS:
-                handleTimeout();
+                mState = STATE_WAIT_FOR_NEXT_MONITORING;
+                addTimer(mState, MONITORING_INTERVAL_MS);
+                break;
+            default:
                 break;
         }
     }
 
     private void queryPowerStatus() {
         sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(),
-                        Constants.ADDR_TV));
+                Constants.ADDR_TV));
 
         mState = STATE_WAIT_FOR_REPORT_POWER_STATUS;
-        addTimer(mState, REPORT_POWER_STATUS_TIMEOUT_MS);
-    }
-
-    private void handleTimeout() {
-        if (mState == STATE_WAIT_FOR_REPORT_POWER_STATUS) {
-            if (mPowerStatusRetries++ < GIVE_POWER_STATUS_FOR_SOURCE_RETRIES) {
-                queryPowerStatus();
-            } else {
-                mPowerStatusRetries = 0;
-                mState = STATE_WAIT_FOR_NEXT_MONITORING;
-                addTimer(mState, MONITORING_INTERVAL_MS);
-            }
-        }
+        addTimer(mState, HdmiConfig.TIMEOUT_MS);
     }
 }
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
index 281db0a..5fe8318 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
@@ -40,6 +40,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.os.Binder;
 import android.os.IBinder;
@@ -58,6 +59,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.inputmethod.SoftInputShowHideReason;
 import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 
 import java.io.PrintWriter;
@@ -78,6 +80,7 @@
     private final int mUserId;
 
     private final InputMethodManagerService mService;
+    private final UserManagerInternal mUserManagerInternal;
     private final WindowManagerInternal mWindowManagerInternal;
 
     final InputMethodManagerService.ImeDisplayValidator mImeDisplayValidator;
@@ -188,6 +191,7 @@
     public ImeVisibilityStateComputer(@NonNull InputMethodManagerService service,
             @UserIdInt int userId) {
         this(service,
+                LocalServices.getService(UserManagerInternal.class),
                 LocalServices.getService(WindowManagerInternal.class),
                 LocalServices.getService(WindowManagerInternal.class)::getDisplayImePolicy,
                 new ImeVisibilityPolicy(), userId);
@@ -196,12 +200,15 @@
     @VisibleForTesting
     public ImeVisibilityStateComputer(@NonNull InputMethodManagerService service,
             @NonNull Injector injector) {
-        this(service, injector.getWmService(), injector.getImeValidator(),
-                new ImeVisibilityPolicy(), injector.getUserId());
+        this(service, injector.getUserManagerService(), injector.getWmService(),
+                injector.getImeValidator(), new ImeVisibilityPolicy(), injector.getUserId());
     }
 
     interface Injector {
         @NonNull
+        UserManagerInternal getUserManagerService();
+
+        @NonNull
         WindowManagerInternal getWmService();
 
         @NonNull
@@ -212,11 +219,13 @@
     }
 
     private ImeVisibilityStateComputer(InputMethodManagerService service,
+            UserManagerInternal userManagerInternal,
             WindowManagerInternal wmService,
             InputMethodManagerService.ImeDisplayValidator imeDisplayValidator,
             ImeVisibilityPolicy imePolicy, @UserIdInt int userId) {
         mUserId = userId;
         mService = service;
+        mUserManagerInternal = userManagerInternal;
         mWindowManagerInternal = wmService;
         mImeDisplayValidator = imeDisplayValidator;
         mPolicy = imePolicy;
@@ -337,7 +346,16 @@
 
     @GuardedBy("ImfLock.class")
     int computeImeDisplayId(@NonNull ImeTargetWindowState state, int displayId) {
-        final int displayToShowIme = computeImeDisplayIdForTarget(displayId, mImeDisplayValidator);
+        final int displayToShowIme;
+        final PackageManager pm = mService.mContext.getPackageManager();
+        if (pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
+                && mUserManagerInternal.isVisibleBackgroundFullUser(mUserId)
+                && Flags.fallbackDisplayForSecondaryUserOnSecondaryDisplay()) {
+            displayToShowIme = mService.computeImeDisplayIdForVisibleBackgroundUserOnAutomotive(
+                    displayId, mUserId, mImeDisplayValidator);
+        } else {
+            displayToShowIme = computeImeDisplayIdForTarget(displayId, mImeDisplayValidator);
+        }
         state.setImeDisplayId(displayToShowIme);
         final boolean imeHiddenByPolicy = displayToShowIme == INVALID_DISPLAY;
         mPolicy.setImeHiddenByDisplayPolicy(imeHiddenByPolicy);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 1f414ac..45c7cff 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2345,8 +2345,32 @@
      * {@link WindowManager#DISPLAY_IME_POLICY_HIDE}
      */
     static int computeImeDisplayIdForTarget(int displayId, @NonNull ImeDisplayValidator checker) {
-        if (displayId == DEFAULT_DISPLAY || displayId == INVALID_DISPLAY) {
-            return FALLBACK_DISPLAY_ID;
+        return computeImeDisplayIdForTargetInner(displayId, checker, FALLBACK_DISPLAY_ID);
+    }
+
+    /**
+     * Find the display where the IME should be shown for a visible background user.
+     *
+     * @param displayId the ID of the display where the IME client target is
+     * @param userId the ID of the user who own the IME
+     * @param checker   instance of {@link ImeDisplayValidator} which is used for
+     *                  checking display config to adjust the final target display
+     * @return the ID of the display where the IME should be shown or
+     * {@link android.view.Display#INVALID_DISPLAY} if the display has an ImePolicy of
+     * {@link WindowManager#DISPLAY_IME_POLICY_HIDE}
+     */
+    int computeImeDisplayIdForVisibleBackgroundUserOnAutomotive(
+            int displayId, @UserIdInt int userId, @NonNull ImeDisplayValidator checker) {
+        // Visible background user can be assigned to a secondary display, not the default display.
+        // The main display assigned to the user will be used as the fallback display.
+        final int mainDisplayId = mUserManagerInternal.getMainDisplayAssignedToUser(userId);
+        return computeImeDisplayIdForTargetInner(displayId, checker, mainDisplayId);
+    }
+
+    private static int computeImeDisplayIdForTargetInner(
+            int displayId, @NonNull ImeDisplayValidator checker, int fallbackDisplayId) {
+        if (displayId == fallbackDisplayId || displayId == INVALID_DISPLAY) {
+            return fallbackDisplayId;
         }
 
         // Show IME window on fallback display when the display doesn't support system decorations
@@ -2356,9 +2380,8 @@
             return displayId;
         } else if (result == DISPLAY_IME_POLICY_HIDE) {
             return INVALID_DISPLAY;
-        } else {
-            return FALLBACK_DISPLAY_ID;
         }
+        return fallbackDisplayId;
     }
 
     @GuardedBy("ImfLock.class")
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java b/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
index a41194b..1949d10 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
@@ -499,9 +499,11 @@
     /* package */
     static HubMessage createHubMessage(Message message) {
         boolean isReliable = (message.flags & Message.FLAG_REQUIRES_DELIVERY_STATUS) != 0;
-        return new HubMessage.Builder(message.type, message.content)
+        HubMessage outMessage = new HubMessage.Builder(message.type, message.content)
                 .setResponseRequired(isReliable)
                 .build();
+        outMessage.setMessageSequenceNumber(message.sequenceNumber);
+        return outMessage;
     }
 
     /**
diff --git a/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java b/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java
index 6e650c2..bf54fd7 100644
--- a/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java
+++ b/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java
@@ -30,6 +30,7 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
@@ -271,9 +272,10 @@
     void unregisterEndpointDiscoveryCallback(IContextHubEndpointDiscoveryCallback callback) {
         Objects.requireNonNull(callback, "callback cannot be null");
         synchronized (mCallbackLock) {
-            for (DiscoveryCallback discoveryCallback : mEndpointDiscoveryCallbacks) {
-                if (discoveryCallback.getCallback().asBinder() == callback.asBinder()) {
-                    mEndpointDiscoveryCallbacks.remove(discoveryCallback);
+            Iterator<DiscoveryCallback> iterator = mEndpointDiscoveryCallbacks.iterator();
+            while (iterator.hasNext()) {
+                if (iterator.next().getCallback().asBinder() == callback.asBinder()) {
+                    iterator.remove();
                     break;
                 }
             }
@@ -303,7 +305,9 @@
             HubEndpointInfo[] endpointInfos,
             BiConsumer<IContextHubEndpointDiscoveryCallback, HubEndpointInfo[]> consumer) {
         synchronized (mCallbackLock) {
-            for (DiscoveryCallback discoveryCallback : mEndpointDiscoveryCallbacks) {
+            Iterator<DiscoveryCallback> iterator = mEndpointDiscoveryCallbacks.iterator();
+            while (iterator.hasNext()) {
+                DiscoveryCallback discoveryCallback = iterator.next();
                 ArrayList<HubEndpointInfo> infoList = new ArrayList<>();
                 for (HubEndpointInfo endpointInfo : endpointInfos) {
                     if (discoveryCallback.isMatch(endpointInfo)) {
diff --git a/services/core/java/com/android/server/location/fudger/LocationFudger.java b/services/core/java/com/android/server/location/fudger/LocationFudger.java
index 28e21b7..39ee0cb 100644
--- a/services/core/java/com/android/server/location/fudger/LocationFudger.java
+++ b/services/core/java/com/android/server/location/fudger/LocationFudger.java
@@ -68,6 +68,17 @@
     private static final double MAX_LATITUDE =
             90.0 - (1.0 / APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR);
 
+    // The average edge length in km of an S2 cell, indexed by S2 levels 0 to
+    // 13. Level 13 is the highest level used for coarsening.
+    // This approximation assumes the S2 cells are squares.
+    // For density-based coarsening, we use the edge to set the accuracy of the
+    // coarsened location.
+    // The values are from http://s2geometry.io/resources/s2cell_statistics.html
+    // We take square root of the average area.
+    private static final float[] S2_CELL_AVG_EDGE_PER_LEVEL = new float[] {
+            9220.14f, 4610.07f, 2305.04f, 1152.52f, 576.26f, 288.13f, 144.06f,
+            72.03f, 36.02f, 20.79f, 9f, 5.05f, 2.25f, 1.13f, 0.57f};
+
     private final float mAccuracyM;
     private final Clock mClock;
     private final Random mRandom;
@@ -194,12 +205,14 @@
         // The new algorithm is applied if and only if (1) the flag is on, (2) the cache has been
         // set, and (3) the cache has successfully queried the provider for the default coarsening
         // value.
+        float accuracy = mAccuracyM;
         if (Flags.populationDensityProvider() && Flags.densityBasedCoarseLocations()
                 && cacheCopy != null) {
             if (cacheCopy.hasDefaultValue()) {
                 // New algorithm that snaps to the center of a S2 cell.
                 int level = cacheCopy.getCoarseningLevel(latitude, longitude);
                 coarsened = snapToCenterOfS2Cell(latitude, longitude, level);
+                accuracy = getS2CellApproximateEdge(level);
             } else {
                 // Try to fetch the default value. The answer won't come in time, but will be used
                 // for the next location to coarsen.
@@ -214,7 +227,7 @@
 
         coarse.setLatitude(coarsened[LAT_INDEX]);
         coarse.setLongitude(coarsened[LNG_INDEX]);
-        coarse.setAccuracy(Math.max(mAccuracyM, coarse.getAccuracy()));
+        coarse.setAccuracy(Math.max(accuracy, coarse.getAccuracy()));
 
         synchronized (this) {
             mCachedFineLocation = fine;
@@ -224,6 +237,19 @@
         return coarse;
     }
 
+    // Returns the average edge length in meters of an S2 cell at the given
+    // level. This is computed as if the S2 cell were a square. We do not need
+    // an exact value, only a rough approximation.
+    @VisibleForTesting
+    protected float getS2CellApproximateEdge(int level) {
+        if (level < 0) {
+            level = 0;
+        } else if (level >= S2_CELL_AVG_EDGE_PER_LEVEL.length) {
+            level = S2_CELL_AVG_EDGE_PER_LEVEL.length - 1;
+        }
+        return S2_CELL_AVG_EDGE_PER_LEVEL[level] * 1000;
+    }
+
     // quantize location by snapping to a grid. this is the primary means of obfuscation. it
     // gives nice consistent results and is very effective at hiding the true location (as
     // long as you are not sitting on a grid boundary, which the random offsets mitigate).
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 0d0cdd8..a0e5433 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -137,6 +137,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.notification.SystemNotificationChannels;
+import com.android.internal.pm.RoSystemFeatures;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IndentingPrintWriter;
@@ -1325,7 +1326,7 @@
         mContext.enforceCallingOrSelfPermission(
                 Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN,
                 "Requires MANAGE_WEAK_ESCROW_TOKEN permission.");
-        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+        if (!RoSystemFeatures.hasFeatureAutomotive(mContext)) {
             throw new IllegalArgumentException(
                     "Weak escrow token are only for automotive devices.");
         }
@@ -3613,7 +3614,7 @@
         }
 
         // Escrow tokens are enabled on automotive builds.
-        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+        if (RoSystemFeatures.hasFeatureAutomotive(mContext)) {
             return;
         }
 
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index d6f7d3b..23e9ac5 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -545,6 +545,16 @@
             for (RoutingSessionInfo session : sessions) {
                 if (session == null) continue;
                 session = assignProviderIdForSession(session);
+
+                if (Flags.enableMirroringInMediaRouter2()) {
+                    var systemSessionCallback =
+                            mSystemSessionCallbacks.get(session.getOriginalId());
+                    if (systemSessionCallback != null) {
+                        systemSessionCallback.onSessionUpdate(session);
+                        continue;
+                    }
+                }
+
                 int sourceIndex = findSessionByIdLocked(session);
                 if (sourceIndex < 0) {
                     mSessionInfos.add(targetIndex++, session);
diff --git a/services/core/java/com/android/server/media/RemoteDisplayProviderProxy.java b/services/core/java/com/android/server/media/RemoteDisplayProviderProxy.java
index ba98a0a..fd1bea9 100644
--- a/services/core/java/com/android/server/media/RemoteDisplayProviderProxy.java
+++ b/services/core/java/com/android/server/media/RemoteDisplayProviderProxy.java
@@ -25,8 +25,9 @@
 import android.media.RemoteDisplayState;
 import android.os.Handler;
 import android.os.IBinder;
-import android.os.RemoteException;
 import android.os.IBinder.DeathRecipient;
+import android.os.Looper;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.Log;
 import android.util.Slog;
@@ -35,10 +36,8 @@
 import java.lang.ref.WeakReference;
 import java.util.Objects;
 
-/**
- * Maintains a connection to a particular remote display provider service.
- */
-final class RemoteDisplayProviderProxy implements ServiceConnection {
+/** Maintains a connection to a particular remote display provider service. */
+final class RemoteDisplayProviderProxy {
     private static final String TAG = "RemoteDisplayProvider";  // max. 23 chars
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
@@ -61,12 +60,15 @@
     private RemoteDisplayState mDisplayState;
     private boolean mScheduledDisplayStateChangedCallback;
 
-    public RemoteDisplayProviderProxy(Context context, ComponentName componentName,
-            int userId) {
+    private final ServiceConnection mServiceConnection =
+            new ServiceConnectionImpl();
+
+    /* package */ RemoteDisplayProviderProxy(
+            Context context, ComponentName componentName, int userId, Looper looper) {
         mContext = context;
         mComponentName = componentName;
         mUserId = userId;
-        mHandler = new Handler();
+        mHandler = new Handler(looper);
     }
 
     public void dump(PrintWriter pw, String prefix) {
@@ -190,9 +192,12 @@
             Intent service = new Intent(RemoteDisplayState.SERVICE_INTERFACE);
             service.setComponent(mComponentName);
             try {
-                mBound = mContext.bindServiceAsUser(service, this,
-                        Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
-                        new UserHandle(mUserId));
+                mBound =
+                        mContext.bindServiceAsUser(
+                                service,
+                                mServiceConnection,
+                                Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
+                                new UserHandle(mUserId));
                 if (!mBound && DEBUG) {
                     Slog.d(TAG, this + ": Bind failed");
                 }
@@ -212,12 +217,11 @@
 
             mBound = false;
             disconnect();
-            mContext.unbindService(this);
+            mContext.unbindService(mServiceConnection);
         }
     }
 
-    @Override
-    public void onServiceConnected(ComponentName name, IBinder service) {
+    private void onServiceConnectedOnHandler(IBinder service) {
         if (DEBUG) {
             Slog.d(TAG, this + ": Connected");
         }
@@ -241,8 +245,7 @@
         }
     }
 
-    @Override
-    public void onServiceDisconnected(ComponentName name) {
+    private void onServiceDisconnectedOnHandler() {
         if (DEBUG) {
             Slog.d(TAG, this + ": Service disconnected");
         }
@@ -322,6 +325,20 @@
         void onDisplayStateChanged(RemoteDisplayProviderProxy provider, RemoteDisplayState state);
     }
 
+    // All methods in this class are called on the main thread.
+    private final class ServiceConnectionImpl implements ServiceConnection {
+
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            mHandler.post(() -> onServiceConnectedOnHandler(service));
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            mHandler.post(RemoteDisplayProviderProxy.this::onServiceDisconnectedOnHandler);
+        }
+    }
+
     private final class Connection implements DeathRecipient {
         private final IRemoteDisplayProvider mProvider;
         private final ProviderCallback mCallback;
diff --git a/services/core/java/com/android/server/media/RemoteDisplayProviderWatcher.java b/services/core/java/com/android/server/media/RemoteDisplayProviderWatcher.java
index 64c451d..cc03c80 100644
--- a/services/core/java/com/android/server/media/RemoteDisplayProviderWatcher.java
+++ b/services/core/java/com/android/server/media/RemoteDisplayProviderWatcher.java
@@ -121,9 +121,11 @@
                 int sourceIndex = findProvider(serviceInfo.packageName, serviceInfo.name);
                 if (sourceIndex < 0) {
                     RemoteDisplayProviderProxy provider =
-                            new RemoteDisplayProviderProxy(mContext,
-                            new ComponentName(serviceInfo.packageName, serviceInfo.name),
-                            mUserId);
+                            new RemoteDisplayProviderProxy(
+                                    mContext,
+                                    new ComponentName(serviceInfo.packageName, serviceInfo.name),
+                                    mUserId,
+                                    mHandler.getLooper());
                     provider.start();
                     mProviders.add(targetIndex++, provider);
                     mCallback.addProvider(provider);
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java
index 8931e3a..011659a 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java
@@ -128,8 +128,20 @@
                                 targetProviderProxyId, existingSession.getProviderId())) {
                     // The currently selected route and target route both belong to the same
                     // provider. We tell the provider to handle the transfer.
-                    targetProviderProxyRecord.requestTransfer(
-                            existingSession.getOriginalId(), serviceTargetRoute);
+                    if (serviceTargetRoute == null) {
+                        notifyRequestFailed(
+                                requestId, MediaRoute2ProviderService.REASON_ROUTE_NOT_AVAILABLE);
+                    } else {
+                        targetProviderProxyRecord.mProxy.transferToRoute(
+                                requestId,
+                                clientUserHandle,
+                                clientPackageName,
+                                existingSession.getOriginalId(),
+                                targetProviderProxyRecord.mNewOriginalIdToSourceOriginalIdMap.get(
+                                        routeOriginalId),
+                                transferReason);
+                    }
+                    return;
                 } else {
                     // The target route is handled by a provider other than the target one. We need
                     // to release the existing session.
@@ -429,11 +441,6 @@
             }
         }
 
-        public void requestTransfer(String sessionId, MediaRoute2Info targetRoute) {
-            // TODO: Map the target route to the source route original id.
-            throw new UnsupportedOperationException("TODO Implement");
-        }
-
         public void releaseSession(long requestId, String originalSessionId) {
             mProxy.releaseSession(requestId, originalSessionId);
         }
@@ -491,18 +498,19 @@
                     () -> {
                         if (mSessionRecord != null) {
                             mSessionRecord.onSessionUpdate(sessionInfo);
+                        } else {
+                            SystemMediaSessionRecord systemMediaSessionRecord =
+                                    new SystemMediaSessionRecord(mProviderId, sessionInfo);
+                            RoutingSessionInfo translatedSession;
+                            synchronized (mLock) {
+                                mSessionRecord = systemMediaSessionRecord;
+                                mPackageNameToSessionRecord.put(
+                                        mClientPackageName, systemMediaSessionRecord);
+                                mPendingSessionCreations.remove(mRequestId);
+                                translatedSession = systemMediaSessionRecord.mTranslatedSessionInfo;
+                            }
+                            onSessionOverrideUpdated(translatedSession);
                         }
-                        SystemMediaSessionRecord systemMediaSessionRecord =
-                                new SystemMediaSessionRecord(mProviderId, sessionInfo);
-                        RoutingSessionInfo translatedSession;
-                        synchronized (mLock) {
-                            mSessionRecord = systemMediaSessionRecord;
-                            mPackageNameToSessionRecord.put(
-                                    mClientPackageName, systemMediaSessionRecord);
-                            mPendingSessionCreations.remove(mRequestId);
-                            translatedSession = systemMediaSessionRecord.mTranslatedSessionInfo;
-                        }
-                        onSessionOverrideUpdated(translatedSession);
                     });
         }
 
@@ -546,7 +554,6 @@
          * The same as {@link #mSourceSessionInfo}, except ids are {@link #asSystemRouteId system
          * provider ids}.
          */
-        @GuardedBy("SystemMediaRoute2Provider2.this.mLock")
         @NonNull
         private RoutingSessionInfo mTranslatedSessionInfo;
 
@@ -559,10 +566,10 @@
 
         @Override
         public void onSessionUpdate(@NonNull RoutingSessionInfo sessionInfo) {
-            RoutingSessionInfo translatedSessionInfo = mTranslatedSessionInfo;
+            RoutingSessionInfo translatedSessionInfo = asSystemProviderSession(sessionInfo);
             synchronized (mLock) {
                 mSourceSessionInfo = sessionInfo;
-                mTranslatedSessionInfo = asSystemProviderSession(sessionInfo);
+                mTranslatedSessionInfo = translatedSessionInfo;
             }
             onSessionOverrideUpdated(translatedSessionInfo);
         }
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java
index d440d3a..efc1b99 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityService.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -110,7 +110,7 @@
             if ((pp.getPackageName() != null && !pp.getPackageName().isEmpty()
                     && !incomingPackageEqualsCallingUidPackage(pp.getPackageName()))
                     && !hasGlobalPictureQualityServicePermission()) {
-                notifyError(null, PictureProfile.ERROR_NO_PERMISSION,
+                notifyOnPictureProfileError(null, PictureProfile.ERROR_NO_PERMISSION,
                         Binder.getCallingUid(), Binder.getCallingPid());
             }
 
@@ -128,7 +128,9 @@
             Long id = db.insert(mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME,
                     null, values);
             populateTempIdMap(mPictureProfileTempIdMap, id);
-            pp.setProfileId(mPictureProfileTempIdMap.getValue(id));
+            String value = mPictureProfileTempIdMap.getValue(id);
+            pp.setProfileId(value);
+            notifyOnPictureProfileAdded(value, pp, Binder.getCallingUid(), Binder.getCallingPid());
             return pp;
         }
 
@@ -136,7 +138,7 @@
         public void updatePictureProfile(String id, PictureProfile pp, UserHandle user) {
             Long dbId = mPictureProfileTempIdMap.getKey(id);
             if (!hasPermissionToUpdatePictureProfile(dbId, pp)) {
-                notifyError(id, PictureProfile.ERROR_NO_PERMISSION,
+                notifyOnPictureProfileError(id, PictureProfile.ERROR_NO_PERMISSION,
                         Binder.getCallingUid(), Binder.getCallingPid());
             }
 
@@ -150,6 +152,8 @@
             SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
             db.replace(mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME,
                     null, values);
+            notifyOnPictureProfileUpdated(mPictureProfileTempIdMap.getValue(dbId),
+                    getPictureProfile(dbId), Binder.getCallingUid(), Binder.getCallingPid());
         }
 
         private boolean hasPermissionToUpdatePictureProfile(Long dbId, PictureProfile toUpdate) {
@@ -164,8 +168,9 @@
         public void removePictureProfile(String id, UserHandle user) {
             Long dbId = mPictureProfileTempIdMap.getKey(id);
 
-            if (!hasPermissionToRemovePictureProfile(dbId)) {
-                notifyError(id, PictureProfile.ERROR_NO_PERMISSION,
+            PictureProfile toDelete = getPictureProfile(dbId);
+            if (!hasPermissionToRemovePictureProfile(toDelete)) {
+                notifyOnPictureProfileError(id, PictureProfile.ERROR_NO_PERMISSION,
                         Binder.getCallingUid(), Binder.getCallingPid());
             }
 
@@ -176,16 +181,20 @@
                 int result = db.delete(mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME, selection,
                         selectionArgs);
                 if (result == 0) {
-                    notifyError(id, PictureProfile.ERROR_INVALID_ARGUMENT,
+                    notifyOnPictureProfileError(id, PictureProfile.ERROR_INVALID_ARGUMENT,
                             Binder.getCallingUid(), Binder.getCallingPid());
                 }
+                notifyOnPictureProfileRemoved(mPictureProfileTempIdMap.getValue(dbId), toDelete,
+                        Binder.getCallingUid(), Binder.getCallingPid());
                 mPictureProfileTempIdMap.remove(dbId);
             }
         }
 
-        private boolean hasPermissionToRemovePictureProfile(Long dbId) {
-            PictureProfile fromDb = getPictureProfile(dbId);
-            return fromDb.getName().equalsIgnoreCase(getPackageOfCallingUid());
+        private boolean hasPermissionToRemovePictureProfile(PictureProfile toDelete) {
+            if (toDelete != null) {
+                return toDelete.getName().equalsIgnoreCase(getPackageOfCallingUid());
+            }
+            return false;
         }
 
         @Override
@@ -246,7 +255,7 @@
         public List<PictureProfile> getPictureProfilesByPackage(
                 String packageName, Bundle options, UserHandle user) {
             if (!hasGlobalPictureQualityServicePermission()) {
-                notifyError(null, PictureProfile.ERROR_NO_PERMISSION,
+                notifyOnPictureProfileError(null, PictureProfile.ERROR_NO_PERMISSION,
                         Binder.getCallingUid(), Binder.getCallingPid());
             }
 
@@ -259,8 +268,7 @@
         }
 
         @Override
-        public List<PictureProfile> getAvailablePictureProfiles(
-                        Bundle options, UserHandle user) {
+        public List<PictureProfile> getAvailablePictureProfiles(Bundle options, UserHandle user) {
             String packageName = getPackageOfCallingUid();
             if (packageName != null) {
                 return getPictureProfilesByPackage(packageName, options, user);
@@ -271,7 +279,7 @@
         @Override
         public boolean setDefaultPictureProfile(String profileId, UserHandle user) {
             if (!hasGlobalPictureQualityServicePermission()) {
-                notifyError(profileId, PictureProfile.ERROR_NO_PERMISSION,
+                notifyOnPictureProfileError(profileId, PictureProfile.ERROR_NO_PERMISSION,
                         Binder.getCallingUid(), Binder.getCallingPid());
             }
             // TODO: pass the profile ID to MediaQuality HAL when ready.
@@ -281,7 +289,7 @@
         @Override
         public List<String> getPictureProfilePackageNames(UserHandle user) {
             if (!hasGlobalPictureQualityServicePermission()) {
-                notifyError(null, PictureProfile.ERROR_NO_PERMISSION,
+                notifyOnPictureProfileError(null, PictureProfile.ERROR_NO_PERMISSION,
                         Binder.getCallingUid(), Binder.getCallingPid());
             }
             String [] column = {BaseParameters.PARAMETER_PACKAGE};
@@ -294,13 +302,31 @@
         }
 
         @Override
-        public List<PictureProfileHandle> getPictureProfileHandle(String[] id, UserHandle user) {
-            return new ArrayList<>();
+        public List<PictureProfileHandle> getPictureProfileHandle(String[] ids, UserHandle user) {
+            List<PictureProfileHandle> toReturn = new ArrayList<>();
+            for (String id : ids) {
+                Long key = mPictureProfileTempIdMap.getKey(id);
+                if (key != null) {
+                    toReturn.add(new PictureProfileHandle(key));
+                } else {
+                    toReturn.add(null);
+                }
+            }
+            return toReturn;
         }
 
         @Override
-        public List<SoundProfileHandle> getSoundProfileHandle(String[] id, UserHandle user) {
-            return new ArrayList<>();
+        public List<SoundProfileHandle> getSoundProfileHandle(String[] ids, UserHandle user) {
+            List<SoundProfileHandle> toReturn = new ArrayList<>();
+            for (String id : ids) {
+                Long key = mSoundProfileTempIdMap.getKey(id);
+                if (key != null) {
+                    toReturn.add(new SoundProfileHandle(key));
+                } else {
+                    toReturn.add(null);
+                }
+            }
+            return toReturn;
         }
 
         @Override
@@ -308,8 +334,8 @@
             if ((sp.getPackageName() != null && !sp.getPackageName().isEmpty()
                     && !incomingPackageEqualsCallingUidPackage(sp.getPackageName()))
                     && !hasGlobalPictureQualityServicePermission()) {
-                //TODO: error handling
-                return null;
+                notifyOnSoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION,
+                        Binder.getCallingUid(), Binder.getCallingPid());
             }
             SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
 
@@ -325,17 +351,18 @@
             Long id = db.insert(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME,
                     null, values);
             populateTempIdMap(mSoundProfileTempIdMap, id);
-            sp.setProfileId(mSoundProfileTempIdMap.getValue(id));
+            String value = mSoundProfileTempIdMap.getValue(id);
+            sp.setProfileId(value);
+            notifyOnSoundProfileAdded(value, sp, Binder.getCallingUid(), Binder.getCallingPid());
             return sp;
         }
 
         @Override
         public void updateSoundProfile(String id, SoundProfile sp, UserHandle user) {
             Long dbId = mSoundProfileTempIdMap.getKey(id);
-
             if (!hasPermissionToUpdateSoundProfile(dbId, sp)) {
-                //TODO: error handling
-                return;
+                notifyOnSoundProfileError(id, SoundProfile.ERROR_NO_PERMISSION,
+                        Binder.getCallingUid(), Binder.getCallingPid());
             }
 
             ContentValues values = getContentValues(dbId,
@@ -347,6 +374,8 @@
 
             SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
             db.replace(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME, null, values);
+            notifyOnSoundProfileUpdated(mSoundProfileTempIdMap.getValue(dbId),
+                    getSoundProfile(dbId), Binder.getCallingUid(), Binder.getCallingPid());
         }
 
         private boolean hasPermissionToUpdateSoundProfile(Long dbId, SoundProfile sp) {
@@ -359,28 +388,34 @@
 
         @Override
         public void removeSoundProfile(String id, UserHandle user) {
-            Long intId = mSoundProfileTempIdMap.getKey(id);
-            if (!hasPermissionToRemoveSoundProfile(intId)) {
-                //TODO: error handling
-                return;
+            Long dbId = mSoundProfileTempIdMap.getKey(id);
+            SoundProfile toDelete = getSoundProfile(dbId);
+            if (!hasPermissionToRemoveSoundProfile(toDelete)) {
+                notifyOnSoundProfileError(id, SoundProfile.ERROR_NO_PERMISSION,
+                        Binder.getCallingUid(), Binder.getCallingPid());
             }
 
-            if (intId != null) {
+            if (dbId != null) {
                 SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
                 String selection = BaseParameters.PARAMETER_ID + " = ?";
-                String[] selectionArgs = {Long.toString(intId)};
+                String[] selectionArgs = {Long.toString(dbId)};
                 int result = db.delete(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME, selection,
                         selectionArgs);
                 if (result == 0) {
-                    //TODO: error handling
+                    notifyOnSoundProfileError(id, SoundProfile.ERROR_INVALID_ARGUMENT,
+                            Binder.getCallingUid(), Binder.getCallingPid());
                 }
-                mSoundProfileTempIdMap.remove(intId);
+                notifyOnSoundProfileRemoved(mSoundProfileTempIdMap.getValue(dbId), toDelete,
+                        Binder.getCallingUid(), Binder.getCallingPid());
+                mSoundProfileTempIdMap.remove(dbId);
             }
         }
 
-        private boolean hasPermissionToRemoveSoundProfile(Long dbId) {
-            SoundProfile fromDb = getSoundProfile(dbId);
-            return fromDb.getName().equalsIgnoreCase(getPackageOfCallingUid());
+        private boolean hasPermissionToRemoveSoundProfile(SoundProfile toDelete) {
+            if (toDelete != null) {
+                return toDelete.getName().equalsIgnoreCase(getPackageOfCallingUid());
+            }
+            return false;
         }
 
         @Override
@@ -403,7 +438,7 @@
                     return null;
                 }
                 if (count > 1) {
-                    Log.wtf(TAG, String.format(Locale.US, "%d entries found for id=%s"
+                    Log.wtf(TAG, String.format(Locale.US, "%d entries found for name=%s"
                                     + " in %s. Should only ever be 0 or 1.", count, name,
                             mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME));
                     return null;
@@ -441,8 +476,8 @@
         public List<SoundProfile> getSoundProfilesByPackage(
                 String packageName, Bundle options, UserHandle user) {
             if (!hasGlobalSoundQualityServicePermission()) {
-                //TODO: error handling
-                return new ArrayList<>();
+                notifyOnSoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION,
+                        Binder.getCallingUid(), Binder.getCallingPid());
             }
 
             boolean includeParams =
@@ -465,8 +500,8 @@
         @Override
         public boolean setDefaultSoundProfile(String profileId, UserHandle user) {
             if (!hasGlobalSoundQualityServicePermission()) {
-                //TODO: error handling
-                return false;
+                notifyOnSoundProfileError(profileId, SoundProfile.ERROR_NO_PERMISSION,
+                        Binder.getCallingUid(), Binder.getCallingPid());
             }
             // TODO: pass the profile ID to MediaQuality HAL when ready.
             return false;
@@ -475,8 +510,8 @@
         @Override
         public List<String> getSoundProfilePackageNames(UserHandle user) {
             if (!hasGlobalSoundQualityServicePermission()) {
-                //TODO: error handling
-                return new ArrayList<>();
+                notifyOnSoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION,
+                        Binder.getCallingUid(), Binder.getCallingPid());
             }
             String [] column = {BaseParameters.PARAMETER_NAME};
             List<SoundProfile> soundProfiles = getSoundProfilesBasedOnConditions(column,
@@ -718,23 +753,134 @@
             }
         }
 
-        private void notifyError(String profileId, int errorCode, int uid, int pid) {
+        enum Mode {
+            ADD,
+            UPDATE,
+            REMOVE,
+            ERROR
+        }
+
+        private void notifyOnPictureProfileAdded(String profileId, PictureProfile profile,
+                int uid, int pid) {
+            notifyPictureProfileHelper(Mode.ADD, profileId, profile, null, uid, pid);
+        }
+
+        private void notifyOnPictureProfileUpdated(String profileId, PictureProfile profile,
+                int uid, int pid) {
+            notifyPictureProfileHelper(Mode.UPDATE, profileId, profile, null, uid, pid);
+        }
+
+        private void notifyOnPictureProfileRemoved(String profileId, PictureProfile profile,
+                int uid, int pid) {
+            notifyPictureProfileHelper(Mode.REMOVE, profileId, profile, null, uid, pid);
+        }
+
+        private void notifyOnPictureProfileError(String profileId, int errorCode,
+                int uid, int pid) {
+            notifyPictureProfileHelper(Mode.ERROR, profileId, null, errorCode, uid, pid);
+        }
+
+        private void notifyPictureProfileHelper(Mode mode, String profileId, PictureProfile profile,
+                Integer errorCode, int uid, int pid) {
             UserState userState = getOrCreateUserStateLocked(UserHandle.USER_SYSTEM);
-            int n = userState.mCallbacks.beginBroadcast();
+            int n = userState.mPictureProfileCallbacks.beginBroadcast();
 
             for (int i = 0; i < n; ++i) {
                 try {
-                    IPictureProfileCallback callback = userState.mCallbacks.getBroadcastItem(i);
-                    Pair<Integer, Integer> pidUid = userState.mCallbackPidUidMap.get(callback);
+                    IPictureProfileCallback callback = userState.mPictureProfileCallbacks
+                            .getBroadcastItem(i);
+                    Pair<Integer, Integer> pidUid = userState.mPictureProfileCallbackPidUidMap
+                            .get(callback);
 
                     if (pidUid.first == pid && pidUid.second == uid) {
-                        userState.mCallbacks.getBroadcastItem(i).onError(profileId, errorCode);
+                        if (mode == Mode.ADD) {
+                            userState.mPictureProfileCallbacks.getBroadcastItem(i)
+                                    .onPictureProfileAdded(profileId, profile);
+                        } else if (mode == Mode.UPDATE) {
+                            userState.mPictureProfileCallbacks.getBroadcastItem(i)
+                                    .onPictureProfileUpdated(profileId, profile);
+                        } else if (mode == Mode.REMOVE) {
+                            userState.mPictureProfileCallbacks.getBroadcastItem(i)
+                                    .onPictureProfileRemoved(profileId, profile);
+                        } else if (mode == Mode.ERROR) {
+                            userState.mPictureProfileCallbacks.getBroadcastItem(i)
+                                    .onError(profileId, errorCode);
+                        }
                     }
                 } catch (RemoteException e) {
-                    Slog.e(TAG, "failed to report added input to callback", e);
+                    if (mode == Mode.ADD) {
+                        Slog.e(TAG, "Failed to report added picture profile to callback", e);
+                    } else if (mode == Mode.UPDATE) {
+                        Slog.e(TAG, "Failed to report updated picture profile to callback", e);
+                    } else if (mode == Mode.REMOVE) {
+                        Slog.e(TAG, "Failed to report removed picture profile to callback", e);
+                    } else if (mode == Mode.ERROR) {
+                        Slog.e(TAG, "Failed to report picture profile error to callback", e);
+                    }
                 }
             }
-            userState.mCallbacks.finishBroadcast();
+            userState.mPictureProfileCallbacks.finishBroadcast();
+        }
+
+        private void notifyOnSoundProfileAdded(String profileId, SoundProfile profile,
+                int uid, int pid) {
+            notifySoundProfileHelper(Mode.ADD, profileId, profile, null, uid, pid);
+        }
+
+        private void notifyOnSoundProfileUpdated(String profileId, SoundProfile profile,
+                int uid, int pid) {
+            notifySoundProfileHelper(Mode.UPDATE, profileId, profile, null, uid, pid);
+        }
+
+        private void notifyOnSoundProfileRemoved(String profileId, SoundProfile profile,
+                int uid, int pid) {
+            notifySoundProfileHelper(Mode.REMOVE, profileId, profile, null, uid, pid);
+        }
+
+        private void notifyOnSoundProfileError(String profileId, int errorCode, int uid, int pid) {
+            notifySoundProfileHelper(Mode.ERROR, profileId, null, errorCode, uid, pid);
+        }
+
+        private void notifySoundProfileHelper(Mode mode, String profileId, SoundProfile profile,
+                Integer errorCode, int uid, int pid) {
+            UserState userState = getOrCreateUserStateLocked(UserHandle.USER_SYSTEM);
+            int n = userState.mSoundProfileCallbacks.beginBroadcast();
+
+            for (int i = 0; i < n; ++i) {
+                try {
+                    ISoundProfileCallback callback = userState.mSoundProfileCallbacks
+                            .getBroadcastItem(i);
+                    Pair<Integer, Integer> pidUid = userState.mSoundProfileCallbackPidUidMap
+                            .get(callback);
+
+                    if (pidUid.first == pid && pidUid.second == uid) {
+                        if (mode == Mode.ADD) {
+                            userState.mSoundProfileCallbacks.getBroadcastItem(i)
+                                    .onSoundProfileAdded(profileId, profile);
+                        } else if (mode == Mode.UPDATE) {
+                            userState.mSoundProfileCallbacks.getBroadcastItem(i)
+                                    .onSoundProfileUpdated(profileId, profile);
+                        } else if (mode == Mode.REMOVE) {
+                            userState.mSoundProfileCallbacks.getBroadcastItem(i)
+                                    .onSoundProfileRemoved(profileId, profile);
+                        } else if (mode == Mode.ERROR) {
+                            userState.mSoundProfileCallbacks.getBroadcastItem(i)
+                                    .onError(profileId, errorCode);
+                        }
+                    }
+                } catch (RemoteException e) {
+                    if (mode == Mode.ADD) {
+                        Slog.e(TAG, "Failed to report added sound profile to callback", e);
+                    } else if (mode == Mode.UPDATE) {
+                        Slog.e(TAG, "Failed to report updated sound profile to callback", e);
+                    } else if (mode == Mode.REMOVE) {
+                        Slog.e(TAG, "Failed to report removed sound profile to callback", e);
+                    } else if (mode == Mode.ERROR) {
+                        Slog.e(TAG, "Failed to report sound profile error to callback", e);
+                    }
+                }
+            }
+            userState.mSoundProfileCallbacks.finishBroadcast();
         }
 
         @Override
@@ -743,11 +889,18 @@
             int callingUid = Binder.getCallingUid();
 
             UserState userState = getOrCreateUserStateLocked(Binder.getCallingUid());
-            userState.mCallbackPidUidMap.put(callback, Pair.create(callingPid, callingUid));
+            userState.mPictureProfileCallbackPidUidMap.put(callback,
+                    Pair.create(callingPid, callingUid));
         }
 
         @Override
         public void registerSoundProfileCallback(final ISoundProfileCallback callback) {
+            int callingPid = Binder.getCallingPid();
+            int callingUid = Binder.getCallingUid();
+
+            UserState userState = getOrCreateUserStateLocked(Binder.getCallingUid());
+            userState.mSoundProfileCallbackPidUidMap.put(callback,
+                    Pair.create(callingPid, callingUid));
         }
 
         @Override
@@ -781,8 +934,8 @@
         @Override
         public List<String> getPictureProfileAllowList(UserHandle user) {
             if (!hasGlobalPictureQualityServicePermission()) {
-                //TODO: error handling
-                return new ArrayList<>();
+                notifyOnPictureProfileError(null, PictureProfile.ERROR_NO_PERMISSION,
+                        Binder.getCallingUid(), Binder.getCallingPid());
             }
             return new ArrayList<>();
         }
@@ -790,15 +943,16 @@
         @Override
         public void setPictureProfileAllowList(List<String> packages, UserHandle user) {
             if (!hasGlobalPictureQualityServicePermission()) {
-                //TODO: error handling
+                notifyOnPictureProfileError(null, PictureProfile.ERROR_NO_PERMISSION,
+                        Binder.getCallingUid(), Binder.getCallingPid());
             }
         }
 
         @Override
         public List<String> getSoundProfileAllowList(UserHandle user) {
             if (!hasGlobalSoundQualityServicePermission()) {
-                //TODO: error handling
-                return new ArrayList<>();
+                notifyOnSoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION,
+                        Binder.getCallingUid(), Binder.getCallingPid());
             }
             return new ArrayList<>();
         }
@@ -806,7 +960,8 @@
         @Override
         public void setSoundProfileAllowList(List<String> packages, UserHandle user) {
             if (!hasGlobalSoundQualityServicePermission()) {
-                //TODO: error handling
+                notifyOnSoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION,
+                        Binder.getCallingUid(), Binder.getCallingPid());
             }
         }
 
@@ -818,7 +973,8 @@
         @Override
         public void setAutoPictureQualityEnabled(boolean enabled, UserHandle user) {
             if (!hasGlobalPictureQualityServicePermission()) {
-                //TODO: error handling
+                notifyOnPictureProfileError(null, PictureProfile.ERROR_NO_PERMISSION,
+                        Binder.getCallingUid(), Binder.getCallingPid());
             }
 
             try {
@@ -849,7 +1005,8 @@
         @Override
         public void setSuperResolutionEnabled(boolean enabled, UserHandle user) {
             if (!hasGlobalPictureQualityServicePermission()) {
-                //TODO: error handling
+                notifyOnPictureProfileError(null, PictureProfile.ERROR_NO_PERMISSION,
+                        Binder.getCallingUid(), Binder.getCallingPid());
             }
 
             try {
@@ -880,7 +1037,8 @@
         @Override
         public void setAutoSoundQualityEnabled(boolean enabled, UserHandle user) {
             if (!hasGlobalSoundQualityServicePermission()) {
-                //TODO: error handling
+                notifyOnSoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION,
+                        Binder.getCallingUid(), Binder.getCallingPid());
             }
 
             try {
@@ -914,7 +1072,7 @@
         }
     }
 
-    private class MediaQualityManagerCallbackList extends
+    private class MediaQualityManagerPictureProfileCallbackList extends
             RemoteCallbackList<IPictureProfileCallback> {
         @Override
         public void onCallbackDied(IPictureProfileCallback callback) {
@@ -922,13 +1080,27 @@
         }
     }
 
+    private class MediaQualityManagerSoundProfileCallbackList extends
+            RemoteCallbackList<ISoundProfileCallback> {
+        @Override
+        public void onCallbackDied(ISoundProfileCallback callback) {
+            //todo
+        }
+    }
+
     private final class UserState {
         // A list of callbacks.
-        private final MediaQualityManagerCallbackList mCallbacks =
-                new MediaQualityManagerCallbackList();
+        private final MediaQualityManagerPictureProfileCallbackList mPictureProfileCallbacks =
+                new MediaQualityManagerPictureProfileCallbackList();
 
-        private final Map<IPictureProfileCallback, Pair<Integer, Integer>> mCallbackPidUidMap =
-                new HashMap<>();
+        private final MediaQualityManagerSoundProfileCallbackList mSoundProfileCallbacks =
+                new MediaQualityManagerSoundProfileCallbackList();
+
+        private final Map<IPictureProfileCallback, Pair<Integer, Integer>>
+                mPictureProfileCallbackPidUidMap = new HashMap<>();
+
+        private final Map<ISoundProfileCallback, Pair<Integer, Integer>>
+                mSoundProfileCallbackPidUidMap = new HashMap<>();
 
         private UserState(Context context, int userId) {
 
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 341038f..837003f 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -27,6 +27,7 @@
 import static android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR;
 import static android.app.Flags.lifetimeExtensionRefactor;
 import static android.app.Flags.notificationClassificationUi;
+import static android.app.Flags.redactSensitiveContentNotificationsOnLockscreen;
 import static android.app.Flags.sortSectionByTime;
 import static android.app.Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
 import static android.app.Notification.EXTRA_BUILDER_APPLICATION_INFO;
@@ -158,6 +159,7 @@
 import static android.view.contentprotection.flags.Flags.rapidClearNotificationsByListenerAppOpEnabled;
 
 import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
+import static com.android.internal.util.FrameworkStatsLog.NOTIFICATION_BUNDLE_PREFERENCES;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES;
@@ -253,6 +255,10 @@
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.metrics.LogMaker;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
@@ -362,6 +368,7 @@
 import com.android.server.notification.GroupHelper.NotificationAttributes;
 import com.android.server.notification.ManagedServices.ManagedServiceInfo;
 import com.android.server.notification.ManagedServices.UserProfiles;
+import com.android.server.notification.NotificationRecordLogger.NotificationPullStatsEvent;
 import com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent;
 import com.android.server.notification.toast.CustomToastRecord;
 import com.android.server.notification.toast.TextToastRecord;
@@ -654,6 +661,7 @@
     private UsageStatsManagerInternal mUsageStatsManagerInternal;
     private TelecomManager mTelecomManager;
     private PowerManager mPowerManager;
+    private ConnectivityManager mConnectivityManager;
     private PostNotificationTrackerFactory mPostNotificationTrackerFactory;
 
     private LockPatternUtils mLockUtils;
@@ -775,6 +783,8 @@
 
     private ModuleInfo mAdservicesModuleInfo;
 
+    private boolean mConnectedToWifi;
+
     static class Archive {
         final SparseArray<Boolean> mEnabled;
         final int mBufferSize;
@@ -2571,6 +2581,7 @@
             TelecomManager telecomManager, NotificationChannelLogger channelLogger,
             SystemUiSystemPropertiesFlags.FlagResolver flagResolver,
             PermissionManager permissionManager, PowerManager powerManager,
+            ConnectivityManager connectivityManager,
             PostNotificationTrackerFactory postNotificationTrackerFactory) {
         mHandler = handler;
         Resources resources = getContext().getResources();
@@ -2603,6 +2614,8 @@
         mUm = userManager;
         mTelecomManager = telecomManager;
         mPowerManager = powerManager;
+        mConnectivityManager = connectivityManager;
+        registerNetworkCallback();
         mPostNotificationTrackerFactory = postNotificationTrackerFactory;
         mPlatformCompat = IPlatformCompat.Stub.asInterface(
                 ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
@@ -2818,6 +2831,36 @@
         mAppOps.startWatchingMode(AppOpsManager.OP_POST_NOTIFICATION, null, mAppOpsListener);
     }
 
+    private void registerNetworkCallback() {
+        NetworkRequest request = new NetworkRequest.Builder().addTransportType(
+                NetworkCapabilities.TRANSPORT_WIFI).build();
+        mConnectivityManager.registerNetworkCallback(request,
+                new ConnectivityManager.NetworkCallback() {
+                // Need to post to another thread, as we can't call synchronous ConnectivityManager
+                // methods from the callback itself, due to potential race conditions.
+                @Override
+                public void onAvailable(@NonNull Network network) {
+                    mHandler.post(() -> updateWifiConnectionState());
+                }
+                @Override
+                public void onLost(@NonNull Network network) {
+                    mHandler.post(() -> updateWifiConnectionState());
+                }
+            });
+        updateWifiConnectionState();
+    }
+
+    @VisibleForTesting()
+    void updateWifiConnectionState() {
+        Network current = mConnectivityManager.getActiveNetwork();
+        NetworkCapabilities capabilities = mConnectivityManager.getNetworkCapabilities(current);
+        if (current == null || capabilities == null) {
+            mConnectedToWifi = false;
+            return;
+        }
+        mConnectedToWifi = capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI);
+    }
+
     /**
      * Cleanup broadcast receivers change listeners.
      */
@@ -2856,6 +2899,7 @@
             mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_PREFERENCES);
             mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES);
             mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES);
+            mStatsManager.clearPullAtomCallback(NOTIFICATION_BUNDLE_PREFERENCES);
             mStatsManager.clearPullAtomCallback(DND_MODE_RULE);
         }
         if (mAppOps != null) {
@@ -2927,6 +2971,7 @@
                 new NotificationChannelLoggerImpl(), SystemUiSystemPropertiesFlags.getResolver(),
                 getContext().getSystemService(PermissionManager.class),
                 getContext().getSystemService(PowerManager.class),
+                getContext().getSystemService(ConnectivityManager.class),
                 new PostNotificationTrackerFactory() {});
 
         publishBinderService(Context.NOTIFICATION_SERVICE, mService, /* allowIsolated= */ false,
@@ -2960,6 +3005,12 @@
                 ConcurrentUtils.DIRECT_EXECUTOR,
                 mPullAtomCallback
         );
+        mStatsManager.setPullAtomCallback(
+                NOTIFICATION_BUNDLE_PREFERENCES,
+                null, // use default PullAtomMetadata values
+                ConcurrentUtils.DIRECT_EXECUTOR,
+                mPullAtomCallback
+        );
     }
 
     private class StatsPullAtomCallbackImpl implements StatsManager.StatsPullAtomCallback {
@@ -2969,6 +3020,7 @@
                 case PACKAGE_NOTIFICATION_PREFERENCES:
                 case PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES:
                 case PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES:
+                case NOTIFICATION_BUNDLE_PREFERENCES:
                 case DND_MODE_RULE:
                     return pullNotificationStates(atomTag, data);
                 default:
@@ -2980,8 +3032,15 @@
     private int pullNotificationStates(int atomTag, List<StatsEvent> data) {
         switch(atomTag) {
             case PACKAGE_NOTIFICATION_PREFERENCES:
-                mPreferencesHelper.pullPackagePreferencesStats(data,
-                        getAllUsersNotificationPermissions());
+                if (notificationClassificationUi()) {
+                    Set<String> pkgs = mAssistants.getPackagesWithKeyTypeAdjustmentSettings();
+                    mPreferencesHelper.pullPackagePreferencesStats(data,
+                            getAllUsersNotificationPermissions(),
+                            getPackageSpecificAdjustmentKeyTypes(pkgs));
+                } else {
+                    mPreferencesHelper.pullPackagePreferencesStats(data,
+                            getAllUsersNotificationPermissions());
+                }
                 break;
             case PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES:
                 mPreferencesHelper.pullPackageChannelPreferencesStats(data);
@@ -2989,6 +3048,11 @@
             case PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES:
                 mPreferencesHelper.pullPackageChannelGroupPreferencesStats(data);
                 break;
+            case NOTIFICATION_BUNDLE_PREFERENCES:
+                if (notificationClassification() && notificationClassificationUi()) {
+                    mAssistants.pullBundlePreferencesStats(data);
+                }
+                break;
             case DND_MODE_RULE:
                 mZenModeHelper.pullRules(data);
                 break;
@@ -4961,6 +5025,12 @@
         @Override
         public ParceledListSlice<NotificationChannel> getNotificationChannels(
                 String callingPkg, String targetPkg, int userId) {
+            return getOrCreateNotificationChannels(callingPkg, targetPkg, userId, false);
+        }
+
+        @Override
+        public ParceledListSlice<NotificationChannel> getOrCreateNotificationChannels(
+                String callingPkg, String targetPkg, int userId, boolean createPrefsIfNeeded) {
             if (canNotifyAsPackage(callingPkg, targetPkg, userId)
                 || isCallingUidSystem()) {
                 int targetUid = -1;
@@ -4970,7 +5040,8 @@
                     /* ignore */
                 }
                 return mPreferencesHelper.getNotificationChannels(
-                        targetPkg, targetUid, false /* includeDeleted */, true);
+                        targetPkg, targetUid, false /* includeDeleted */, true,
+                        createPrefsIfNeeded);
             }
             throw new SecurityException("Pkg " + callingPkg
                     + " cannot read channels for " + targetPkg + " in " + userId);
@@ -7481,6 +7552,24 @@
         return allPermissions;
     }
 
+    @VisibleForTesting
+    @FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
+    protected @NonNull Map<String, Set<Integer>> getPackageSpecificAdjustmentKeyTypes(
+            Set<String> pkgs) {
+        ArrayMap<String, Set<Integer>> pkgToAllowedTypes = new ArrayMap<>();
+        for (String pkg : pkgs) {
+            int[] allowedTypesArray = mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg);
+            if (allowedTypesArray != null) {
+                Set<Integer> allowedTypes = new ArraySet<Integer>();
+                for (int i : allowedTypesArray) {
+                    allowedTypes.add(i);
+                }
+                pkgToAllowedTypes.append(pkg, allowedTypes);
+            }
+        }
+        return pkgToAllowedTypes;
+    }
+
     private void dumpJson(PrintWriter pw, @NonNull DumpFilter filter,
             ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions) {
         JSONObject dump = new JSONObject();
@@ -10388,16 +10477,12 @@
     }
 
     private void scheduleListenerHintsChanged(int state) {
-        if (!Flags.notificationReduceMessagequeueUsage()) {
-            mHandler.removeMessages(MESSAGE_LISTENER_HINTS_CHANGED);
-        }
+        mHandler.removeMessages(MESSAGE_LISTENER_HINTS_CHANGED);
         mHandler.obtainMessage(MESSAGE_LISTENER_HINTS_CHANGED, state, 0).sendToTarget();
     }
 
     private void scheduleInterruptionFilterChanged(int listenerInterruptionFilter) {
-        if (!Flags.notificationReduceMessagequeueUsage()) {
-            mHandler.removeMessages(MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED);
-        }
+        mHandler.removeMessages(MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED);
         mHandler.obtainMessage(
                 MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED,
                 listenerInterruptionFilter,
@@ -10477,14 +10562,9 @@
         }
 
         protected void scheduleSendRankingUpdate() {
-            if (Flags.notificationReduceMessagequeueUsage()) {
+            if (!hasMessages(MESSAGE_SEND_RANKING_UPDATE)) {
                 Message m = Message.obtain(this, MESSAGE_SEND_RANKING_UPDATE);
                 sendMessage(m);
-            } else {
-                if (!hasMessages(MESSAGE_SEND_RANKING_UPDATE)) {
-                    Message m = Message.obtain(this, MESSAGE_SEND_RANKING_UPDATE);
-                    sendMessage(m);
-                }
             }
         }
 
@@ -10493,12 +10573,8 @@
             if (lifetimeExtensionRefactor()) {
                 sendMessageDelayed(Message.obtain(this, cancelRunnable), delay);
             } else {
-                if (Flags.notificationReduceMessagequeueUsage()) {
+                if (!hasCallbacks(cancelRunnable)) {
                     sendMessage(Message.obtain(this, cancelRunnable));
-                } else {
-                    if (!hasCallbacks(cancelRunnable)) {
-                        sendMessage(Message.obtain(this, cancelRunnable));
-                    }
                 }
             }
         }
@@ -10533,9 +10609,7 @@
         }
 
         public void requestSort() {
-            if (!Flags.notificationReduceMessagequeueUsage()) {
-                removeMessages(MESSAGE_RANKING_SORT);
-            }
+            removeMessages(MESSAGE_RANKING_SORT);
             Message msg = Message.obtain();
             msg.what = MESSAGE_RANKING_SORT;
             sendMessage(msg);
@@ -11613,12 +11687,20 @@
                     new NotificationListenerService.Ranking();
             ArrayList<Notification.Action> smartActions = record.getSystemGeneratedSmartActions();
             ArrayList<CharSequence> smartReplies = record.getSmartReplies();
-            if (redactSensitiveNotificationsFromUntrustedListeners()
-                    && info != null
-                    && !mListeners.isUidTrusted(info.uid)
-                    && mListeners.hasSensitiveContent(record)) {
-                smartActions = null;
-                smartReplies = null;
+            boolean hasSensitiveContent = record.hasSensitiveContent();
+            if (redactSensitiveNotificationsFromUntrustedListeners()) {
+                if (!mListeners.isUidTrusted(info.uid) && mListeners.hasSensitiveContent(record)) {
+                    smartActions = null;
+                    smartReplies = null;
+                }
+                if (redactSensitiveContentNotificationsOnLockscreen()) {
+                    if (mListeners.hasSensitiveContent(record) && mConnectedToWifi
+                            && info.isSystemUi) {
+                        // We don't inform systemUI of sensitive content if
+                        // connected to wifi, though we do still redact from untrusted listeners.
+                        hasSensitiveContent = false;
+                    }
+                }
             }
             ranking.populate(
                     key,
@@ -11648,7 +11730,7 @@
                             : (record.getRankingScore() > 0 ?  RANKING_PROMOTED : RANKING_DEMOTED),
                     record.getNotification().isBubbleNotification(),
                     record.getProposedImportance(),
-                    record.hasSensitiveContent()
+                    hasSensitiveContent
             );
             rankings.add(ranking);
         }
@@ -12056,6 +12138,22 @@
         }
 
         @FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
+        protected @NonNull Set<String> getPackagesWithKeyTypeAdjustmentSettings() {
+            if (notificationClassificationUi()) {
+                Set<String> packagesWithModifications = new ArraySet<String>();
+                synchronized (mLock) {
+                    for (String pkg : mClassificationTypePackagesEnabledTypes.keySet()) {
+                        if (mClassificationTypePackagesEnabledTypes.get(pkg) != null) {
+                            packagesWithModifications.add(pkg);
+                        }
+                    }
+                }
+                return packagesWithModifications;
+            }
+            return new ArraySet<String>();
+        }
+
+        @FlaggedApi(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
         protected @NonNull int[] getAllowedAdjustmentKeyTypesForPackage(String pkg) {
             synchronized (mLock) {
                 if (notificationClassificationUi()) {
@@ -12656,6 +12754,32 @@
                 Slog.e(TAG, "unable to notify assistant (capabilities): " + info, ex);
             }
         }
+
+        /**
+         * Fills out {@link BundlePreferences} proto and wraps it in a {@link StatsEvent}.
+         */
+        @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+        protected void pullBundlePreferencesStats(List<StatsEvent> events) {
+            boolean bundlesAllowed = true;
+            synchronized (mLock) {
+                List<String> unsupportedAdjustments = new ArrayList(
+                        mNasUnsupported.getOrDefault(
+                                UserHandle.getUserId(Binder.getCallingUid()),
+                                new HashSet<>())
+                );
+                bundlesAllowed = !unsupportedAdjustments.contains(Adjustment.KEY_TYPE);
+            }
+
+            int[] allowedBundleTypes = getAllowedAdjustmentKeyTypes();
+
+            events.add(FrameworkStatsLog.buildStatsEvent(
+                    NOTIFICATION_BUNDLE_PREFERENCES,
+                    /* optional int32 event_id = 1 */
+                    NotificationPullStatsEvent.NOTIFICATION_BUNDLE_PREFERENCES_PULLED.getId(),
+                    /* optional bool bundles_allowed = 2 */ bundlesAllowed,
+                    /* repeated android.stats.notification.BundleTypes allowed_bundle_types = 3 */
+                    allowedBundleTypes));
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
index 3943aa5..6c0035b 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
@@ -32,8 +32,6 @@
 import android.service.notification.NotificationStats;
 import android.util.Log;
 
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags;
 import com.android.internal.logging.InstanceId;
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
@@ -368,6 +366,19 @@
         }
     }
 
+    enum NotificationPullStatsEvent implements UiEventLogger.UiEventEnum {
+        @UiEvent(doc = "Notification Bundle Preferences pulled.")
+        NOTIFICATION_BUNDLE_PREFERENCES_PULLED(2072);
+
+        private final int mId;
+        NotificationPullStatsEvent(int id) {
+            mId = id;
+        }
+        @Override public int getId() {
+            return mId;
+        }
+    }
+
     /**
      * A helper for extracting logging information from one or two NotificationRecords.
      */
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 36eabae..3b34dcd 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -16,6 +16,7 @@
 
 package com.android.server.notification;
 
+import static android.app.Flags.notificationClassificationUi;
 import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
 import static android.app.NotificationChannel.DEFAULT_CHANNEL_ID;
 import static android.app.NotificationChannel.NEWS_ID;
@@ -1961,10 +1962,25 @@
     @Override
     public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
             boolean includeDeleted, boolean includeBundles) {
+        return getNotificationChannels(pkg, uid, includeDeleted, includeBundles, false);
+    }
+
+    protected ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
+            boolean includeDeleted, boolean includeBundles, boolean createPrefsIfNeeded) {
+        if (createPrefsIfNeeded && !android.app.Flags.nmBinderPerfCacheChannels()) {
+            Slog.wtf(TAG,
+                    "getNotificationChannels called with createPrefsIfNeeded=true and flag off");
+            createPrefsIfNeeded = false;
+        }
         Objects.requireNonNull(pkg);
         List<NotificationChannel> channels = new ArrayList<>();
         synchronized (mLock) {
-            PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
+            PackagePreferences r;
+            if (createPrefsIfNeeded) {
+                r = getOrCreatePackagePreferencesLocked(pkg, uid);
+            } else {
+                r = getPackagePreferencesLocked(pkg, uid);
+            }
             if (r == null) {
                 return ParceledListSlice.emptyList();
             }
@@ -2523,6 +2539,25 @@
      */
     public void pullPackagePreferencesStats(List<StatsEvent> events,
             ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions) {
+        pullPackagePreferencesStats(events, pkgPermissions, new ArrayMap<String, Set<Integer>>());
+    }
+
+
+    /**
+     * Fills out {@link PackageNotificationPreferences} proto and wraps it in a {@link StatsEvent}.
+     * @param events Newly filled out StatsEvent protos are added to this list as output.
+     * @param pkgPermissions Maps from a pair representing a uid and package to a pair of booleans,
+     *                       where the first represents whether the notification permission was
+     *                       granted to that package, and the second represents whether the
+     *                       permission was user-set.
+     * @param pkgAdjustmentKeyTypes A map of package names that are not allowed to have their
+     *                                 notifications classified into differently typed notification
+     *                                 channels, and the channels that they're allowed to be
+     *                                 classified into.
+     */
+    public void pullPackagePreferencesStats(List<StatsEvent> events,
+            ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions,
+            @NonNull Map<String, Set<Integer>> pkgAdjustmentKeyTypes) {
         Set<Pair<Integer, String>> pkgsWithPermissionsToHandle = null;
         if (pkgPermissions != null) {
             pkgsWithPermissionsToHandle = pkgPermissions.keySet();
@@ -2568,6 +2603,14 @@
                         isFsiPermissionUserSet(r.pkg, r.uid, fsiState,
                                 currentPermissionFlags);
 
+                if (!notificationClassificationUi()
+                        && pkgAdjustmentKeyTypes.keySet().size() > 0) {
+                    Slog.w(TAG, "Pkg adjustment types improperly allowed without flag set");
+                }
+
+                int[] allowedBundleTypes =
+                        getAllowedTypesForPackage(pkgAdjustmentKeyTypes, r.pkg);
+
                 events.add(FrameworkStatsLog.buildStatsEvent(
                         PACKAGE_NOTIFICATION_PREFERENCES,
                         /* optional int32 uid = 1 [(is_uid) = true] */ r.uid,
@@ -2576,7 +2619,9 @@
                         /* optional int32 user_locked_fields = 4 */ r.lockedAppFields,
                         /* optional bool user_set_importance = 5 */ importanceIsUserSet,
                         /* optional FsiState fsi_state = 6 */ fsiState,
-                        /* optional bool is_fsi_permission_user_set = 7 */ fsiIsUserSet));
+                        /* optional bool is_fsi_permission_user_set = 7 */ fsiIsUserSet,
+                        /* repeated int32 allowed_bundle_types = 8 */ allowedBundleTypes
+                ));
             }
         }
 
@@ -2587,6 +2632,10 @@
                     break;
                 }
                 pulledEvents++;
+
+                int[] allowedBundleTypes =
+                        getAllowedTypesForPackage(pkgAdjustmentKeyTypes, p.second);
+
                 // Because all fields are required in FrameworkStatsLog.buildStatsEvent, we have
                 // to fill in default values for all the unspecified fields.
                 events.add(FrameworkStatsLog.buildStatsEvent(
@@ -2598,11 +2647,31 @@
                         /* optional int32 user_locked_fields = 4 */ DEFAULT_LOCKED_APP_FIELDS,
                         /* optional bool user_set_importance = 5 */ pkgPermissions.get(p).second,
                         /* optional FsiState fsi_state = 6 */ 0,
-                        /* optional bool is_fsi_permission_user_set = 7 */ false));
+                        /* optional bool is_fsi_permission_user_set = 7 */ false,
+                        /* repeated BundleTypes allowed_bundle_types = 8 */ allowedBundleTypes));
             }
         }
     }
 
+    private int[] getAllowedTypesForPackage(@NonNull
+                                            Map<String, Set<Integer>> pkgAdjustmentKeyTypes,
+                                            String pkg) {
+        int[] allowedBundleTypes = new int[]{};
+        if (notificationClassificationUi()) {
+            if (pkgAdjustmentKeyTypes.containsKey(pkg)) {
+                // Convert from set to int[]
+                Set<Integer> types = pkgAdjustmentKeyTypes.get(pkg);
+                allowedBundleTypes = new int[types.size()];
+                int i = 0;
+                for (int val : types) {
+                    allowedBundleTypes[i] = val;
+                    i++;
+                }
+            }
+        }
+        return allowedBundleTypes;
+    }
+
     /**
      * Fills out {@link PackageNotificationChannelPreferences} proto and wraps it in a
      * {@link StatsEvent}.
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index c1ca9c2..b4a8aee 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -51,13 +51,6 @@
 }
 
 flag {
-  name: "notification_reduce_messagequeue_usage"
-  namespace: "systemui"
-  description: "When this flag is on, NMS will no longer call removeMessage() and hasCallbacks() on Handler"
-  bug: "311051285"
-}
-
-flag {
   name: "notification_test"
   namespace: "systemui"
   description: "Timing test, no functionality"
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index 3660607..bf0e77e 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -86,8 +86,6 @@
  */
 public final class BroadcastHelper {
     private static final boolean DEBUG_BROADCASTS = false;
-    private static final String PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED =
-            "android.permission.INTERNAL_RECEIVE_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED";
 
     private final UserManagerInternal mUmInternal;
     private final ActivityManagerInternal mAmInternal;
@@ -398,8 +396,7 @@
                 sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp,
                         notExportedComponentNames, packageUid, reason, userIds, instantUserIds,
                         broadcastAllowList, "android" /* targetPackageName */,
-                        new String[]{
-                                PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED});
+                        null /* requiredPermissions */);
             }
 
             // Second, send the PACKAGE_CHANGED broadcast to the application itself.
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index f6e518a..90adb66 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -729,10 +729,13 @@
         final String internalPackageName =
                 snapshot.resolveInternalPackageName(packageName, versionCode);
 
+        final boolean deleteAllUsers = (deleteFlags & PackageManager.DELETE_ALL_USERS) != 0;
+        final int[] users = deleteAllUsers ? mUserManagerInternal.getUserIds() : new int[]{userId};
+
         if (!isOrphaned(snapshot, internalPackageName)
                 && !allowSilentUninstall
-                && !isCallerAllowedToSilentlyUninstall(
-                        snapshot, callingUid, internalPackageName, userId)) {
+                && !isCallerAllowedToSilentlyUninstall(snapshot, callingUid, internalPackageName,
+                users)) {
             mPm.mHandler.post(() -> {
                 try {
                     final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE);
@@ -751,8 +754,7 @@
             });
             return;
         }
-        final boolean deleteAllUsers = (deleteFlags & PackageManager.DELETE_ALL_USERS) != 0;
-        final int[] users = deleteAllUsers ? mUserManagerInternal.getUserIds() : new int[]{userId};
+
         if (UserHandle.getUserId(callingUid) != userId || (deleteAllUsers && users.length > 1)) {
             mPm.mContext.enforceCallingOrSelfPermission(
                     android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
@@ -916,16 +918,24 @@
     }
 
     private boolean isCallerAllowedToSilentlyUninstall(@NonNull Computer snapshot, int callingUid,
-            String pkgName, int userId) {
+            String pkgName, int[] targetUserIds) {
         if (PackageManagerServiceUtils.isRootOrShell(callingUid)
                 || UserHandle.getAppId(callingUid) == Process.SYSTEM_UID) {
             return true;
         }
         final int callingUserId = UserHandle.getUserId(callingUid);
+
         // If the caller installed the pkgName, then allow it to silently uninstall.
-        if (callingUid == snapshot.getPackageUid(
-                snapshot.getInstallerPackageName(pkgName, userId), 0, callingUserId)) {
-            return true;
+        for (int user : targetUserIds) {
+            try {
+                if (callingUid == snapshot.getPackageUid(
+                        snapshot.getInstallerPackageName(pkgName, user), 0, callingUserId)) {
+                    return true;
+                }
+            } catch (Exception ignored) {
+                // The app to be uninstalled (`pkgName`) is not installed on this `user`. Continue
+                // looking for the installerPkgName in the next user
+            }
         }
 
         // Allow package verifier to silently uninstall.
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index a8f31f9..4cca855 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -3039,13 +3039,14 @@
             if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()
                     && android.security.Flags.extendEcmToAllSettings()) {
                 final int appId = request.getAppId();
-                mPm.mHandler.post(() -> {
+                // TODO: b/388960315 - Implement a long-term solution to race condition
+                mPm.mHandler.postDelayed(() -> {
                     for (int userId : firstUserIds) {
                         // MODE_DEFAULT means that the app's guardedness will be decided lazily
                         setAccessRestrictedSettingsMode(packageName, appId, userId,
                                 AppOpsManager.MODE_DEFAULT);
                     }
-                });
+                }, 1000L);
             } else {
                 // Apply restricted settings on potentially dangerous packages. Needs to happen
                 // after appOpsManager is notified of the new package
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index fb16b86..a902f5f 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -1848,8 +1848,10 @@
         boolean manifestOverrideEnabled =  (mPageSizeAppCompatFlags
                 & ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_MANIFEST_OVERRIDE_ENABLED) != 0;
         boolean settingsOverrideEnabled =  (mPageSizeAppCompatFlags
-                & ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_MANIFEST_OVERRIDE_ENABLED) != 0;
-        if (manifestOverrideEnabled || settingsOverrideEnabled) {
+                & ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_ENABLED) != 0;
+        boolean settingsOverrideDisabled =  (mPageSizeAppCompatFlags
+                & ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_DISABLED) != 0;
+        if (manifestOverrideEnabled || settingsOverrideEnabled || settingsOverrideDisabled) {
             return null;
         }
 
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 81956fb..b85e6894 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -33,6 +33,7 @@
 import static android.os.UserManager.USER_OPERATION_ERROR_UNKNOWN;
 import static android.os.UserManager.USER_OPERATION_ERROR_USER_RESTRICTED;
 import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
+import static android.os.UserManager.supportsMultipleUsers;
 import static android.provider.Settings.Secure.HIDE_PRIVATESPACE_ENTRY_POINT;
 
 import static com.android.internal.app.SetScreenLockDialogActivity.EXTRA_ORIGIN_USER_ID;
@@ -1156,7 +1157,7 @@
 
         showHsumNotificationIfNeeded();
 
-        if (Flags.addUiForSoundsFromBackgroundUsers()) {
+        if (shouldShowNotificationForBackgroundUserSounds()) {
             new BackgroundUserSoundNotifier(mContext);
         }
     }
@@ -3312,13 +3313,18 @@
         }
     }
 
-
-
     private void sendUserInfoChangedBroadcast(@UserIdInt int userId) {
         Intent changedIntent = new Intent(Intent.ACTION_USER_INFO_CHANGED);
         changedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
         changedIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
         mContext.sendBroadcastAsUser(changedIntent, UserHandle.ALL);
+
+        // This intent allow system UI apps to refresh the content even if process was freezed.
+        Intent bgIntent = new Intent(Intent.ACTION_USER_INFO_CHANGED_BACKGROUND);
+        bgIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+        bgIntent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+        mContext.sendBroadcastAsUser(bgIntent, UserHandle.ALL,
+                Manifest.permission.MANAGE_USERS);
     }
 
     @Override
@@ -8481,6 +8487,17 @@
     }
 
     /**
+     * @hide
+     * Checks whether to show a notification for sounds (e.g., alarms, timers, etc.) from
+     * background users.
+     */
+    public static boolean shouldShowNotificationForBackgroundUserSounds() {
+        return Flags.addUiForSoundsFromBackgroundUsers() && Resources.getSystem().getBoolean(
+                com.android.internal.R.bool.config_showNotificationForBackgroundUserAlarms)
+                && supportsMultipleUsers();
+    }
+
+    /**
      * Returns instance of {@link com.android.server.pm.UserJourneyLogger}.
      */
     public UserJourneyLogger getUserJourneyLogger() {
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index e75f852e..a755ee1 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -45,6 +45,7 @@
 import com.android.server.input.InputManagerInternal;
 import com.android.server.policy.devicestate.config.Conditions;
 import com.android.server.policy.devicestate.config.DeviceStateConfig;
+import com.android.server.policy.devicestate.config.Flags;
 import com.android.server.policy.devicestate.config.LidSwitchCondition;
 import com.android.server.policy.devicestate.config.NumericRange;
 import com.android.server.policy.devicestate.config.Properties;
@@ -140,7 +141,16 @@
     private static final String PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT =
             "com.android.server.policy.PROPERTY_FEATURE_REAR_DISPLAY_OUTER_DEFAULT";
 
-
+    // Deprecated flag definitions to maintain backwards compatibility.
+    private static final String FLAG_CANCEL_OVERRIDE_REQUESTS = "FLAG_CANCEL_OVERRIDE_REQUESTS";
+    private static final String FLAG_APP_INACCESSIBLE = "FLAG_APP_INACCESSIBLE";
+    private static final String FLAG_EMULATED_ONLY = "FLAG_EMULATED_ONLY";
+    private static final String FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP =
+            "FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP";
+    private static final String FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL =
+            "FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL";
+    private static final String FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE =
+            "FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE";
 
     /** Interface that allows reading the device state configuration. */
     interface ReadableConfig {
@@ -185,15 +195,29 @@
                             new HashSet<>();
                     Set<@DeviceState.DeviceStateProperties Integer> physicalProperties =
                             new HashSet<>();
-                    final Properties configFlags = stateConfig.getProperties();
-                    if (configFlags != null) {
-                        List<String> configPropertyStrings = configFlags.getProperty();
+                    final Properties configProperties = stateConfig.getProperties();
+                    if (configProperties != null) {
+                        List<String> configPropertyStrings = configProperties.getProperty();
                         for (int i = 0; i < configPropertyStrings.size(); i++) {
                             final String configPropertyString = configPropertyStrings.get(i);
                             addPropertyByString(configPropertyString, systemProperties,
                                     physicalProperties);
                         }
                     }
+
+                    if (android.hardware.devicestate.feature.flags
+                            .Flags.deviceStateConfigurationFlag()) {
+                        // Parse through the deprecated flag configuration to keep compatibility.
+                        final Flags configFlags = stateConfig.getFlags();
+                        if (configFlags != null) {
+                            List<String> configFlagStrings = configFlags.getFlag();
+                            for (int i = 0; i < configFlagStrings.size(); i++) {
+                                final String configFlagString = configFlagStrings.get(i);
+                                addFlagByString(configFlagString, systemProperties);
+                            }
+                        }
+                    }
+
                     DeviceState.Configuration deviceStateConfiguration =
                             new DeviceState.Configuration.Builder(state, name)
                                     .setSystemProperties(systemProperties)
@@ -292,6 +316,34 @@
         }
     }
 
+    private static void addFlagByString(String flagString,
+            Set<@DeviceState.SystemDeviceStateProperties Integer> systemProperties) {
+        switch (flagString) {
+            case FLAG_APP_INACCESSIBLE:
+                systemProperties.add(DeviceState.PROPERTY_APP_INACCESSIBLE);
+                break;
+            case FLAG_EMULATED_ONLY:
+                systemProperties.add(DeviceState.PROPERTY_EMULATED_ONLY);
+                break;
+            case FLAG_CANCEL_OVERRIDE_REQUESTS:
+                systemProperties.add(DeviceState.PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS);
+                break;
+            case FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP:
+                systemProperties.add(DeviceState.PROPERTY_POLICY_CANCEL_WHEN_REQUESTER_NOT_ON_TOP);
+                break;
+            case FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE:
+                systemProperties.add(DeviceState.PROPERTY_POLICY_UNSUPPORTED_WHEN_POWER_SAVE_MODE);
+                break;
+            case FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL:
+                systemProperties.add(
+                        DeviceState.PROPERTY_POLICY_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL);
+                break;
+            default:
+                Slog.w(TAG, "Parsed unknown flag with name: " + flagString);
+                break;
+        }
+    }
+
     // Lock for internal state.
     private final Object mLock = new Object();
     private final Context mContext;
diff --git a/services/core/java/com/android/server/power/TEST_MAPPING b/services/core/java/com/android/server/power/TEST_MAPPING
index f67f56d..c5937e5 100644
--- a/services/core/java/com/android/server/power/TEST_MAPPING
+++ b/services/core/java/com/android/server/power/TEST_MAPPING
@@ -8,6 +8,12 @@
     },
     {
       "name": "PowerServiceTests_server_power"
+    },
+    {
+      "name": "CtsStatsdAtomHostTestCases_statsdatom_powermanager",
+      "file_patterns": [
+        "(/|^)ThermalManagerService.java"
+      ]
     }
   ],
   "postsubmit": [
@@ -22,12 +28,6 @@
     },
     {
       "name": "PowerServiceTests_server_power"
-    },
-    {
-      "name": "CtsStatsdAtomHostTestCases_statsdatom_powermanager",
-      "file_patterns": [
-        "(/|^)ThermalManagerService.java"
-      ]
     }
   ]
 }
diff --git a/services/core/java/com/android/server/power/hint/TEST_MAPPING b/services/core/java/com/android/server/power/hint/TEST_MAPPING
index 5450700..fd81277 100644
--- a/services/core/java/com/android/server/power/hint/TEST_MAPPING
+++ b/services/core/java/com/android/server/power/hint/TEST_MAPPING
@@ -15,5 +15,22 @@
         {"exclude-annotation": "org.junit.Ignore"}
       ]
     }
+  ],
+  "postsubmit": [
+    {
+      "name": "CtsSystemHealthTestCases",
+      "options": [
+        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+        {"exclude-annotation": "org.junit.Ignore"}
+      ]
+    },
+    {
+      "name": "CtsOsTestCases",
+      "options": [
+        {"include-filter": "android.os.health.cts.HeadroomTest"},
+        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+        {"exclude-annotation": "org.junit.Ignore"}
+      ]
+    }
   ]
 }
\ No newline at end of file
diff --git a/packages/CrashRecovery/framework/java/android/service/watchdog/PackageConfig.aidl b/services/core/java/com/android/server/selinux/QuotaExceededException.java
similarity index 64%
copy from packages/CrashRecovery/framework/java/android/service/watchdog/PackageConfig.aidl
copy to services/core/java/com/android/server/selinux/QuotaExceededException.java
index 0131586..26d4d827 100644
--- a/packages/CrashRecovery/framework/java/android/service/watchdog/PackageConfig.aidl
+++ b/services/core/java/com/android/server/selinux/QuotaExceededException.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 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.
@@ -13,10 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package com.android.server.selinux;
 
-package android.service.watchdog;
-
-/**
- * @hide
+/** An exception raised when the quota has been reached.
+ *
+ * This exception is raised in EventLogCollection.add(). See QuotaLimiter
+ * for the implementation details.
  */
-parcelable PackageConfig;
+class QuotaExceededException extends Exception {}
diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java
index 0aa7058..54365ff 100644
--- a/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java
+++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java
@@ -28,10 +28,8 @@
 
 import java.io.IOException;
 import java.time.Instant;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Queue;
+import java.util.AbstractCollection;
+import java.util.Iterator;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Supplier;
 import java.util.regex.Matcher;
@@ -57,6 +55,7 @@
     private final Supplier<String> mAuditDomainSupplier;
     private final RateLimiter mRateLimiter;
     private final QuotaLimiter mQuotaLimiter;
+    private EventLogCollection mEventCollection;
 
     @VisibleForTesting Instant mLastWrite = Instant.MIN;
 
@@ -69,6 +68,7 @@
         mAuditDomainSupplier = auditDomainSupplier;
         mRateLimiter = rateLimiter;
         mQuotaLimiter = quotaLimiter;
+        mEventCollection = new EventLogCollection();
     }
 
     SelinuxAuditLogsCollector(RateLimiter rateLimiter, QuotaLimiter quotaLimiter) {
@@ -86,75 +86,72 @@
         mStopRequested.set(stopRequested);
     }
 
-    /**
-     * Collect and push SELinux audit logs for the provided {@code tagCode}.
+    /** A Collection to work around EventLog.readEvents() constraints.
      *
-     * @return true if the job was completed. If the job was interrupted, return false.
+     * This collection only supports add(). Any other method inherited from
+     * Collection will throw an UnsupportedOperationException exception.
+     *
+     * This collection ensures that we are processing one event at a time and
+     * avoid collecting all the event objects before processing (e.g.,
+     * ArrayList), which could lead to an OOM situation.
      */
-    boolean collect(int tagCode) {
-        Queue<Event> logLines = new ArrayDeque<>();
-        Instant latestTimestamp = collectLogLines(tagCode, logLines);
+    class EventLogCollection extends AbstractCollection<Event> {
 
-        boolean quotaExceeded = writeAuditLogs(logLines);
-        if (quotaExceeded) {
-            Slog.w(TAG, "Too many SELinux logs in the queue, I am giving up.");
-            mLastWrite = latestTimestamp; // next run we will ignore all these logs.
-            logLines.clear();
+        SelinuxAuditLogBuilder mAuditLogBuilder;
+        int mAuditsWritten = 0;
+        Instant mLatestTimestamp;
+
+        void reset() {
+            mAuditsWritten = 0;
+            mLatestTimestamp = mLastWrite;
+            mAuditLogBuilder = new SelinuxAuditLogBuilder(mAuditDomainSupplier.get());
         }
 
-        return logLines.isEmpty();
-    }
-
-    private Instant collectLogLines(int tagCode, Queue<Event> logLines) {
-        List<Event> events = new ArrayList<>();
-        try {
-            EventLog.readEvents(new int[] {tagCode}, events);
-        } catch (IOException e) {
-            Slog.e(TAG, "Error reading event logs", e);
+        int getAuditsWritten() {
+            return mAuditsWritten;
         }
 
-        Instant latestTimestamp = mLastWrite;
-        for (Event event : events) {
-            Instant eventTime = Instant.ofEpochSecond(0, event.getTimeNanos());
-            if (eventTime.isAfter(latestTimestamp)) {
-                latestTimestamp = eventTime;
+        Instant getLatestTimestamp() {
+            return mLatestTimestamp;
+        }
+
+        @Override
+        public Iterator<Event> iterator() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public int size() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public boolean add(Event event) {
+            if (mStopRequested.get()) {
+                throw new IllegalStateException(new InterruptedException());
             }
+
+            Instant eventTime = Instant.ofEpochSecond(/* epochSecond= */ 0, event.getTimeNanos());
             if (eventTime.compareTo(mLastWrite) <= 0) {
-                continue;
+                return true;
             }
             Object eventData = event.getData();
             if (!(eventData instanceof String)) {
-                continue;
+                return true;
             }
-            logLines.add(event);
-        }
-        return latestTimestamp;
-    }
-
-    private boolean writeAuditLogs(Queue<Event> logLines) {
-        final SelinuxAuditLogBuilder auditLogBuilder =
-                new SelinuxAuditLogBuilder(mAuditDomainSupplier.get());
-        int auditsWritten = 0;
-
-        while (!mStopRequested.get() && !logLines.isEmpty()) {
-            Event event = logLines.poll();
-            String logLine = (String) event.getData();
-            Instant logTime = Instant.ofEpochSecond(0, event.getTimeNanos());
+            String logLine = (String) eventData;
             if (!SELINUX_MATCHER.reset(logLine).matches()) {
-                continue;
+                return true;
             }
 
-            auditLogBuilder.reset(SELINUX_MATCHER.group("denial"));
-            final SelinuxAuditLog auditLog = auditLogBuilder.build();
+            mAuditLogBuilder.reset(SELINUX_MATCHER.group("denial"));
+            final SelinuxAuditLog auditLog = mAuditLogBuilder.build();
             if (auditLog == null) {
-                continue;
+                return true;
             }
 
             if (!mQuotaLimiter.acquire()) {
-                if (DEBUG) {
-                    Slogf.d(TAG, "Running out of quota after %d logs.", auditsWritten);
-                }
-                return true;
+                throw new IllegalStateException(new QuotaExceededException());
             }
             mRateLimiter.acquire();
 
@@ -169,16 +166,50 @@
                     auditLog.mTClass,
                     auditLog.mPath,
                     auditLog.mPermissive);
-            auditsWritten++;
 
-            if (logTime.isAfter(mLastWrite)) {
-                mLastWrite = logTime;
+            mAuditsWritten++;
+            if (eventTime.isAfter(mLatestTimestamp)) {
+                mLatestTimestamp = eventTime;
             }
+
+            return true;
+        }
+    }
+
+    /**
+     * Collect and push SELinux audit logs for the provided {@code tagCode}.
+     *
+     * @return true if the job was completed. If the job was interrupted or
+     * failed because of IOException, return false.
+     * @throws QuotaExceededException if it ran out of quota.
+     */
+    boolean collect(int tagCode) throws QuotaExceededException {
+        mEventCollection.reset();
+        try {
+            EventLog.readEvents(new int[] {tagCode}, mEventCollection);
+        } catch (IllegalStateException e) {
+            if (e.getCause() instanceof QuotaExceededException) {
+                if (DEBUG) {
+                    Slogf.d(TAG, "Running out of quota after %d logs.",
+                            mEventCollection.getAuditsWritten());
+                }
+                // next run we will ignore all these logs.
+                mLastWrite = mEventCollection.getLatestTimestamp();
+                throw (QuotaExceededException) e.getCause();
+            } else if (e.getCause() instanceof InterruptedException) {
+                mLastWrite = mEventCollection.getLatestTimestamp();
+                return false;
+            }
+            throw e;
+        } catch (IOException e) {
+            Slog.e(TAG, "Error reading event logs", e);
+            return false;
         }
 
+        mLastWrite = mEventCollection.getLatestTimestamp();
         if (DEBUG) {
-            Slogf.d(TAG, "Written %d logs", auditsWritten);
+            Slogf.d(TAG, "Written %d logs", mEventCollection.getAuditsWritten());
         }
-        return false;
+        return true;
     }
 }
diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogsJob.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogsJob.java
index 0092c37..e55e590 100644
--- a/services/core/java/com/android/server/selinux/SelinuxAuditLogsJob.java
+++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogsJob.java
@@ -51,8 +51,12 @@
             return;
         }
         mIsRunning.set(true);
-        boolean done = mAuditLogsCollector.collect(SelinuxAuditLogsService.AUDITD_TAG_CODE);
-        if (done) {
+        try {
+            boolean done = mAuditLogsCollector.collect(SelinuxAuditLogsService.AUDITD_TAG_CODE);
+            if (done) {
+                jobService.jobFinished(params, /* wantsReschedule= */ false);
+            }
+        } catch (QuotaExceededException e) {
             jobService.jobFinished(params, /* wantsReschedule= */ false);
         }
         mIsRunning.set(false);
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index a19a342..b7b4cc0 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -95,7 +95,6 @@
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
-import android.view.accessibility.Flags;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
@@ -981,16 +980,12 @@
 
         @Override
         public void addQsTileToFrontOrEnd(ComponentName tile, boolean end) {
-            if (Flags.a11yQsShortcut()) {
-                StatusBarManagerService.this.addQsTileToFrontOrEnd(tile, end);
-            }
+            StatusBarManagerService.this.addQsTileToFrontOrEnd(tile, end);
         }
 
         @Override
         public void removeQsTile(ComponentName tile) {
-            if (Flags.a11yQsShortcut()) {
-                StatusBarManagerService.this.remTile(tile);
-            }
+            StatusBarManagerService.this.remTile(tile);
         }
     };
 
@@ -1098,19 +1093,7 @@
     }
 
     public void addTile(ComponentName component) {
-        if (Flags.a11yQsShortcut()) {
-            addQsTileToFrontOrEnd(component, false);
-        } else {
-            enforceStatusBarOrShell();
-            enforceValidCallingUser();
-
-            if (mBar != null) {
-                try {
-                    mBar.addQsTile(component);
-                } catch (RemoteException ex) {
-                }
-            }
-        }
+        addQsTileToFrontOrEnd(component, false);
     }
 
     private void addQsTileToFrontOrEnd(ComponentName tile, boolean end) {
diff --git a/services/core/java/com/android/server/timezonedetector/Environment.java b/services/core/java/com/android/server/timezonedetector/Environment.java
new file mode 100644
index 0000000..795fb02
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/Environment.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 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.server.timezonedetector;
+
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.ElapsedRealtimeLong;
+import android.annotation.NonNull;
+
+import com.android.server.SystemTimeZone;
+
+import java.io.PrintWriter;
+
+/**
+ * Used by the time zone detector code to interact with device state besides that available from
+ * {@link ServiceConfigAccessor}. It can be faked for testing.
+ */
+public interface Environment {
+
+    /**
+     * Returns the device's currently configured time zone. May return an empty string.
+     */
+    @NonNull
+    String getDeviceTimeZone();
+
+    /**
+     * Returns the confidence of the device's current time zone.
+     */
+    @SystemTimeZone.TimeZoneConfidence
+    int getDeviceTimeZoneConfidence();
+
+    /**
+     * Sets the device's time zone, associated confidence, and records a debug log entry.
+     */
+    void setDeviceTimeZoneAndConfidence(
+            @NonNull String zoneId, @SystemTimeZone.TimeZoneConfidence int confidence,
+            @NonNull String logInfo);
+
+    /**
+     * Returns the time according to the elapsed realtime clock, the same as {@link
+     * android.os.SystemClock#elapsedRealtime()}.
+     */
+    @ElapsedRealtimeLong
+    long elapsedRealtimeMillis();
+
+    /**
+     * Returns the current time in milliseconds, the same as
+     * {@link java.lang.System#currentTimeMillis()}.
+     */
+    @CurrentTimeMillisLong
+    long currentTimeMillis();
+
+    /**
+     * Adds a standalone entry to the time zone debug log.
+     */
+    void addDebugLogEntry(@NonNull String logMsg);
+
+    /**
+     * Dumps the time zone debug log to the supplied {@link PrintWriter}.
+     */
+    void dumpDebugLog(PrintWriter printWriter);
+
+    /**
+     * Requests that the supplied runnable be invoked asynchronously.
+     */
+    void runAsync(@NonNull Runnable runnable);
+}
diff --git a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
index 449b41a..8491b48 100644
--- a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
@@ -16,6 +16,7 @@
 
 package com.android.server.timezonedetector;
 
+import android.annotation.CurrentTimeMillisLong;
 import android.annotation.ElapsedRealtimeLong;
 import android.annotation.NonNull;
 import android.os.Handler;
@@ -31,9 +32,9 @@
 import java.util.Objects;
 
 /**
- * The real implementation of {@link TimeZoneDetectorStrategyImpl.Environment}.
+ * The real implementation of {@link Environment}.
  */
-final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Environment {
+final class EnvironmentImpl implements Environment {
 
     private static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
 
@@ -69,6 +70,11 @@
     }
 
     @Override
+    public @CurrentTimeMillisLong long currentTimeMillis() {
+        return System.currentTimeMillis();
+    }
+
+    @Override
     public void addDebugLogEntry(@NonNull String logMsg) {
         SystemTimeZone.addDebugLogEntry(logMsg);
     }
diff --git a/services/core/java/com/android/server/timezonedetector/NotifyingTimeZoneChangeListener.java b/services/core/java/com/android/server/timezonedetector/NotifyingTimeZoneChangeListener.java
index 2e73829..cf85a9a 100644
--- a/services/core/java/com/android/server/timezonedetector/NotifyingTimeZoneChangeListener.java
+++ b/services/core/java/com/android/server/timezonedetector/NotifyingTimeZoneChangeListener.java
@@ -29,6 +29,7 @@
 
 import android.annotation.DurationMillisLong;
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.UserIdInt;
 import android.app.ActivityManagerInternal;
@@ -44,7 +45,6 @@
 import android.icu.text.SimpleDateFormat;
 import android.icu.util.TimeZone;
 import android.os.Handler;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.util.IndentingPrintWriter;
 import android.util.Log;
@@ -153,6 +153,9 @@
         }
     };
 
+    @NonNull
+    private final Environment mEnvironment;
+
     private final Object mConfigurationLock = new Object();
     @GuardedBy("mConfigurationLock")
     private ConfigurationInternal mConfigurationInternal;
@@ -170,12 +173,14 @@
     /** Create and initialise a new {@code TimeZoneChangeTrackerImpl} */
     @RequiresPermission("android.permission.INTERACT_ACROSS_USERS_FULL")
     public static NotifyingTimeZoneChangeListener create(Handler handler, Context context,
-            ServiceConfigAccessor serviceConfigAccessor) {
+            ServiceConfigAccessor serviceConfigAccessor,
+            @NonNull Environment environment) {
         NotifyingTimeZoneChangeListener changeTracker =
                 new NotifyingTimeZoneChangeListener(handler,
                         context,
                         serviceConfigAccessor,
-                        context.getSystemService(NotificationManager.class));
+                        context.getSystemService(NotificationManager.class),
+                        environment);
 
         // Pretend there was an update to initialize configuration.
         changeTracker.handleConfigurationUpdate();
@@ -184,9 +189,9 @@
     }
 
     @VisibleForTesting
-    NotifyingTimeZoneChangeListener(
-            Handler handler, Context context, ServiceConfigAccessor serviceConfigAccessor,
-            NotificationManager notificationManager) {
+    NotifyingTimeZoneChangeListener(Handler handler, Context context,
+            ServiceConfigAccessor serviceConfigAccessor, NotificationManager notificationManager,
+            @NonNull Environment environment) {
         mHandler = Objects.requireNonNull(handler);
         mContext = Objects.requireNonNull(context);
         mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor);
@@ -194,6 +199,7 @@
                 this::handleConfigurationUpdate);
         mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
         mNotificationManager = notificationManager;
+        mEnvironment = Objects.requireNonNull(environment);
     }
 
     @RequiresPermission("android.permission.INTERACT_ACROSS_USERS_FULL")
@@ -420,7 +426,7 @@
             if (!changeEvent.getOldZoneId().equals(lastChangeEvent.getNewZoneId())) {
                 int changeEventId = mNextChangeEventId.getAndIncrement();
                 TimeZoneChangeEvent syntheticChangeEvent = new TimeZoneChangeEvent(
-                        SystemClock.elapsedRealtime(), System.currentTimeMillis(),
+                        mEnvironment.elapsedRealtimeMillis(), mEnvironment.currentTimeMillis(),
                         ORIGIN_UNKNOWN, UserHandle.USER_NULL, lastChangeEvent.getNewZoneId(),
                         changeEvent.getOldZoneId(), 0, "Synthetic");
                 TimeZoneChangeRecord syntheticTrackedChangeEvent =
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
index b2b06b0..042d81a 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
@@ -25,7 +25,6 @@
 import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_HIGH;
 import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_LOW;
 
-import android.annotation.ElapsedRealtimeLong;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -54,7 +53,6 @@
 import com.android.server.flags.Flags;
 import com.android.server.timezonedetector.ConfigurationInternal.DetectionMode;
 
-import java.io.PrintWriter;
 import java.time.Duration;
 import java.util.ArrayList;
 import java.util.List;
@@ -67,55 +65,6 @@
  */
 public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrategy {
 
-    /**
-     * Used by {@link TimeZoneDetectorStrategyImpl} to interact with device state besides that
-     * available from {@link #mServiceConfigAccessor}. It can be faked for testing.
-     */
-    @VisibleForTesting
-    public interface Environment {
-
-        /**
-         * Returns the device's currently configured time zone. May return an empty string.
-         */
-        @NonNull
-        String getDeviceTimeZone();
-
-        /**
-         * Returns the confidence of the device's current time zone.
-         */
-        @TimeZoneConfidence
-        int getDeviceTimeZoneConfidence();
-
-        /**
-         * Sets the device's time zone, associated confidence, and records a debug log entry.
-         */
-        void setDeviceTimeZoneAndConfidence(
-                @NonNull String zoneId, @TimeZoneConfidence int confidence,
-                @NonNull String logInfo);
-
-        /**
-         * Returns the time according to the elapsed realtime clock, the same as {@link
-         * android.os.SystemClock#elapsedRealtime()}.
-         */
-        @ElapsedRealtimeLong
-        long elapsedRealtimeMillis();
-
-        /**
-         * Adds a standalone entry to the time zone debug log.
-         */
-        void addDebugLogEntry(@NonNull String logMsg);
-
-        /**
-         * Dumps the time zone debug log to the supplied {@link PrintWriter}.
-         */
-        void dumpDebugLog(PrintWriter printWriter);
-
-        /**
-         * Requests that the supplied runnable be invoked asynchronously.
-         */
-        void runAsync(@NonNull Runnable runnable);
-    }
-
     private static final String LOG_TAG = TimeZoneDetectorService.TAG;
     private static final boolean DBG = TimeZoneDetectorService.DBG;
 
@@ -263,10 +212,10 @@
     public static TimeZoneDetectorStrategyImpl create(
             @NonNull Context context, @NonNull Handler handler,
             @NonNull ServiceConfigAccessor serviceConfigAccessor) {
-
         Environment environment = new EnvironmentImpl(handler);
         TimeZoneChangeListener changeEventTracker =
-                NotifyingTimeZoneChangeListener.create(handler, context, serviceConfigAccessor);
+                NotifyingTimeZoneChangeListener.create(handler, context, serviceConfigAccessor,
+                        environment);
         return new TimeZoneDetectorStrategyImpl(
                 serviceConfigAccessor, environment, changeEventTracker);
     }
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index ae726c1..a580504 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -79,6 +79,7 @@
 import com.android.internal.util.DumpUtils;
 import com.android.server.SystemService;
 import com.android.server.pm.BackgroundUserSoundNotifier;
+import com.android.server.pm.UserManagerService;
 import com.android.server.vibrator.VibrationSession.CallerInfo;
 import com.android.server.vibrator.VibrationSession.DebugInfo;
 import com.android.server.vibrator.VibrationSession.Status;
@@ -200,7 +201,7 @@
                             VibratorManagerService.this::shouldCancelOnScreenOffLocked,
                             Status.CANCELLED_BY_SCREEN_OFF);
                 }
-            } else if (android.multiuser.Flags.addUiForSoundsFromBackgroundUsers()
+            } else if (UserManagerService.shouldShowNotificationForBackgroundUserSounds()
                     && intent.getAction().equals(BackgroundUserSoundNotifier.ACTION_MUTE_SOUND)) {
                 synchronized (mLock) {
                     maybeClearCurrentAndNextSessionsLocked(
@@ -324,7 +325,7 @@
 
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_SCREEN_OFF);
-        if (android.multiuser.Flags.addUiForSoundsFromBackgroundUsers()) {
+        if (UserManagerService.shouldShowNotificationForBackgroundUserSounds()) {
             filter.addAction(BackgroundUserSoundNotifier.ACTION_MUTE_SOUND);
         }
         context.registerReceiver(mIntentReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index d31aed2..a24522a5 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -90,10 +90,6 @@
 import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
-import static android.content.pm.ActivityInfo.SIZE_CHANGES_SUPPORTED_METADATA;
-import static android.content.pm.ActivityInfo.SIZE_CHANGES_SUPPORTED_OVERRIDE;
-import static android.content.pm.ActivityInfo.SIZE_CHANGES_UNSUPPORTED_METADATA;
-import static android.content.pm.ActivityInfo.SIZE_CHANGES_UNSUPPORTED_OVERRIDE;
 import static android.content.res.Configuration.ASSETS_SEQ_UNDEFINED;
 import static android.content.res.Configuration.EMPTY;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
@@ -486,6 +482,7 @@
     final String launchedFromPackage; // always the package who started the activity.
     @Nullable
     final String launchedFromFeatureId; // always the feature in launchedFromPackage
+    @LaunchSourceType
     int mLaunchSourceType; // latest launch source type
     final Intent intent;    // the original intent that generated us
     final String shortComponentName; // the short component name of the intent
@@ -1272,8 +1269,9 @@
                 pw.println(prefix + "manifestMinAspectRatio="
                         + info.getManifestMinAspectRatio());
             }
-            pw.println(prefix + "supportsSizeChanges="
-                    + ActivityInfo.sizeChangesSupportModeToString(supportsSizeChanges()));
+            pw.println(
+                    prefix + "supportsSizeChanges=" + ActivityInfo.sizeChangesSupportModeToString(
+                            mAppCompatController.getSizeCompatModePolicy().supportsSizeChanges()));
             if (info.configChanges != 0) {
                 pw.println(prefix + "configChanges=0x" + Integer.toHexString(info.configChanges));
             }
@@ -2333,6 +2331,7 @@
         mLaunchSourceType = determineLaunchSourceType(launchFromUid, caller);
     }
 
+    @LaunchSourceType
     private int determineLaunchSourceType(int launchFromUid, WindowProcessController caller) {
         if (launchFromUid == Process.SYSTEM_UID || launchFromUid == Process.ROOT_UID) {
             return LAUNCH_SOURCE_TYPE_SYSTEM;
@@ -3232,7 +3231,7 @@
             return false;
         }
         // If the user preference respects aspect ratio, then it becomes non-resizable.
-        return mAppCompatController.getAppCompatAspectRatioOverrides()
+        return mAppCompatController.getAspectRatioOverrides()
                 .userPreferenceCompatibleWithNonResizability();
     }
 
@@ -5477,7 +5476,9 @@
     }
 
     boolean canAffectSystemUiFlags() {
-        return task != null && task.canAffectSystemUiFlags() && isVisible()
+        final TaskFragment taskFragment = getTaskFragment();
+        return taskFragment != null && taskFragment.canAffectSystemUiFlags()
+                && isVisible()
                 && !mWaitForEnteringPinnedMode && !inPinnedWindowingMode();
     }
 
@@ -6581,7 +6582,7 @@
         mTaskSupervisor.mStoppingActivities.remove(this);
         if (getDisplayArea().allResumedActivitiesComplete()) {
             // Construct the compat environment at a relatively stable state if needed.
-            mAppCompatController.getAppCompatSizeCompatModePolicy().updateAppCompatDisplayInsets();
+            mAppCompatController.getSizeCompatModePolicy().updateAppCompatDisplayInsets();
             mRootWindowContainer.executeAppTransitionForAllDisplay();
         }
 
@@ -8222,7 +8223,7 @@
                     != getRequestedConfigurationOrientation(false /*forDisplay */)) {
             // Do not change the requested configuration now, because this will be done when setting
             // the orientation below with the new mAppCompatDisplayInsets
-            mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatModeAttributes();
+            mAppCompatController.getSizeCompatModePolicy().clearSizeCompatModeAttributes();
         }
         ProtoLog.v(WM_DEBUG_ORIENTATION,
                 "Setting requested orientation %s for %s",
@@ -8366,7 +8367,7 @@
 
     @Nullable
     AppCompatDisplayInsets getAppCompatDisplayInsets() {
-        return mAppCompatController.getAppCompatSizeCompatModePolicy().getAppCompatDisplayInsets();
+        return mAppCompatController.getSizeCompatModePolicy().getAppCompatDisplayInsets();
     }
 
     /**
@@ -8374,31 +8375,7 @@
      *         density than its parent or its bounds don't fit in parent naturally.
      */
     boolean inSizeCompatMode() {
-        final AppCompatSizeCompatModePolicy scmPolicy = mAppCompatController
-                .getAppCompatSizeCompatModePolicy();
-        if (scmPolicy.isInSizeCompatModeForBounds()) {
-            return true;
-        }
-        if (getAppCompatDisplayInsets() == null || !shouldCreateAppCompatDisplayInsets()
-                // The orientation is different from parent when transforming.
-                || isFixedRotationTransforming()) {
-            return false;
-        }
-        final Rect appBounds = getConfiguration().windowConfiguration.getAppBounds();
-        if (appBounds == null) {
-            // The app bounds hasn't been computed yet.
-            return false;
-        }
-        final WindowContainer parent = getParent();
-        if (parent == null) {
-            // The parent of detached Activity can be null.
-            return false;
-        }
-        final Configuration parentConfig = parent.getConfiguration();
-        // Although colorMode, screenLayout, smallestScreenWidthDp are also fixed, generally these
-        // fields should be changed with density and bounds, so here only compares the most
-        // significant field.
-        return parentConfig.densityDpi != getConfiguration().densityDpi;
+        return mAppCompatController.getSizeCompatModePolicy().inSizeCompatMode();
     }
 
     /**
@@ -8412,67 +8389,12 @@
      *         aspect ratio.
      */
     boolean shouldCreateAppCompatDisplayInsets() {
-        if (mAppCompatController.getAppCompatAspectRatioOverrides().hasFullscreenOverride()) {
-            // If the user has forced the applications aspect ratio to be fullscreen, don't use size
-            // compatibility mode in any situation. The user has been warned and therefore accepts
-            // the risk of the application misbehaving.
-            return false;
-        }
-        switch (supportsSizeChanges()) {
-            case SIZE_CHANGES_SUPPORTED_METADATA:
-            case SIZE_CHANGES_SUPPORTED_OVERRIDE:
-                return false;
-            case SIZE_CHANGES_UNSUPPORTED_OVERRIDE:
-                return true;
-            default:
-                // Fall through
-        }
-        // Use root activity's info for tasks in multi-window mode, or fullscreen tasks in freeform
-        // task display areas, to ensure visual consistency across activity launches and exits in
-        // the same task.
-        final TaskDisplayArea tda = getTaskDisplayArea();
-        if (inMultiWindowMode() || (tda != null && tda.inFreeformWindowingMode())) {
-            final ActivityRecord root = task != null ? task.getRootActivity() : null;
-            if (root != null && root != this && !root.shouldCreateAppCompatDisplayInsets()) {
-                // If the root activity doesn't use size compatibility mode, the activities above
-                // are forced to be the same for consistent visual appearance.
-                return false;
-            }
-        }
-        return !isResizeable() && (info.isFixedOrientation() || hasFixedAspectRatio())
-                // The configuration of non-standard type should be enforced by system.
-                // {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD} is set when this activity is
-                // added to a task, but this function is called when resolving the launch params, at
-                // which point, the activity type is still undefined if it will be standard.
-                // For other non-standard types, the type is set in the constructor, so this should
-                // not be a problem.
-                && isActivityTypeStandardOrUndefined();
-    }
-
-    /**
-     * Returns whether the activity supports size changes.
-     */
-    @ActivityInfo.SizeChangesSupportMode
-    private int supportsSizeChanges() {
-        final AppCompatResizeOverrides resizeOverrides = mAppCompatController.getResizeOverrides();
-        if (resizeOverrides.shouldOverrideForceNonResizeApp()) {
-            return SIZE_CHANGES_UNSUPPORTED_OVERRIDE;
-        }
-
-        if (info.supportsSizeChanges) {
-            return SIZE_CHANGES_SUPPORTED_METADATA;
-        }
-
-        if (resizeOverrides.shouldOverrideForceResizeApp()) {
-            return SIZE_CHANGES_SUPPORTED_OVERRIDE;
-        }
-
-        return SIZE_CHANGES_UNSUPPORTED_METADATA;
+        return mAppCompatController.getSizeCompatModePolicy().shouldCreateAppCompatDisplayInsets();
     }
 
     @Override
     boolean hasSizeCompatBounds() {
-        return mAppCompatController.getAppCompatSizeCompatModePolicy().hasSizeCompatBounds();
+        return mAppCompatController.getSizeCompatModePolicy().hasSizeCompatBounds();
     }
 
     @Override
@@ -8491,7 +8413,7 @@
     @Override
     float getCompatScale() {
         // We need to invoke {#getCompatScale()} only if the CompatScale is not available.
-        return mAppCompatController.getAppCompatSizeCompatModePolicy()
+        return mAppCompatController.getSizeCompatModePolicy()
                 .getCompatScaleIfAvailable(ActivityRecord.super::getCompatScale);
     }
 
@@ -8518,7 +8440,7 @@
             newParentConfiguration = mTmpConfig;
         }
 
-        mAppCompatController.getAppCompatAspectRatioPolicy().reset();
+        mAppCompatController.getAspectRatioPolicy().reset();
         mIsEligibleForFixedOrientationLetterbox = false;
         mResolveConfigHint.resolveTmpOverrides(mDisplayContent, newParentConfiguration,
                 isFixedRotationTransforming());
@@ -8549,15 +8471,15 @@
         // If activity in fullscreen mode is letterboxed because of fixed orientation then bounds
         // are already calculated in resolveFixedOrientationConfiguration.
         // Don't apply aspect ratio if app is overridden to fullscreen by device user/manufacturer.
-        if (!mAppCompatController.getAppCompatAspectRatioPolicy()
+        if (!mAppCompatController.getAspectRatioPolicy()
                     .isLetterboxedForFixedOrientationAndAspectRatio()
-                && !mAppCompatController.getAppCompatAspectRatioOverrides()
+                && !mAppCompatController.getAspectRatioOverrides()
                     .hasFullscreenOverride()) {
             resolveAspectRatioRestriction(newParentConfiguration);
         }
         final AppCompatDisplayInsets appCompatDisplayInsets = getAppCompatDisplayInsets();
         final AppCompatSizeCompatModePolicy scmPolicy =
-                mAppCompatController.getAppCompatSizeCompatModePolicy();
+                mAppCompatController.getSizeCompatModePolicy();
         if (appCompatDisplayInsets != null) {
             scmPolicy.resolveSizeCompatModeConfiguration(newParentConfiguration,
                     appCompatDisplayInsets, mTmpBounds);
@@ -8586,7 +8508,7 @@
                 // Fixed orientation letterboxing is possible on both large screen devices
                 // with ignoreOrientationRequest enabled and on phones in split screen even with
                 // ignoreOrientationRequest disabled.
-                && (mAppCompatController.getAppCompatAspectRatioPolicy()
+                && (mAppCompatController.getAspectRatioPolicy()
                     .isLetterboxedForFixedOrientationAndAspectRatio()
                         // Limiting check for aspect ratio letterboxing to devices with enabled
                         // ignoreOrientationRequest. This avoids affecting phones where apps may
@@ -8595,7 +8517,7 @@
                         // accurate on phones shouldn't make the big difference and is expected
                         // to be already well-tested by apps.
                         || (isIgnoreOrientationRequest
-                && mAppCompatController.getAppCompatAspectRatioPolicy().isAspectRatioApplied()))) {
+                && mAppCompatController.getAspectRatioPolicy().isAspectRatioApplied()))) {
             // TODO(b/264034555): Use mDisplayContent to calculate smallestScreenWidthDp from all
             // rotations and only re-calculate if parent bounds have non-orientation size change.
             resolvedConfig.smallestScreenWidthDp =
@@ -8707,7 +8629,7 @@
             return mAppCompatController.getTransparentPolicy().getInheritedAppCompatState();
         }
         final AppCompatSizeCompatModePolicy scmPolicy = mAppCompatController
-                .getAppCompatSizeCompatModePolicy();
+                .getSizeCompatModePolicy();
         if (scmPolicy.isInSizeCompatModeForBounds()) {
             return APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE;
         }
@@ -8715,13 +8637,13 @@
         // letterboxed for fixed orientation. Aspect ratio restrictions are also applied if
         // present. But this doesn't return true when the activity is letterboxed only because
         // of aspect ratio restrictions.
-        if (mAppCompatController.getAppCompatAspectRatioPolicy()
-                .isLetterboxedForFixedOrientationAndAspectRatio()) {
+        final AppCompatAspectRatioPolicy aspectRatioPolicy =
+                mAppCompatController.getAspectRatioPolicy();
+        if (aspectRatioPolicy.isLetterboxedForFixedOrientationAndAspectRatio()) {
             return APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION;
         }
         // Letterbox for limited aspect ratio.
-        if (mAppCompatController.getAppCompatAspectRatioPolicy()
-                .isLetterboxedForAspectRatioOnly()) {
+        if (aspectRatioPolicy.isLetterboxedForAspectRatioOnly()) {
             return APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO;
         }
 
@@ -8745,7 +8667,7 @@
             return;
         }
         final AppCompatSizeCompatModePolicy scmPolicy =
-                mAppCompatController.getAppCompatSizeCompatModePolicy();
+                mAppCompatController.getSizeCompatModePolicy();
         final Rect screenResolvedBounds = scmPolicy.replaceResolvedBoundsIfNeeded(resolvedBounds);
         final Rect parentAppBounds = mResolveConfigHint.mParentAppBoundsOverride;
         final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
@@ -8843,7 +8765,7 @@
         final Configuration resolvedConfig = getResolvedOverrideConfiguration();
         final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
         final AppCompatSizeCompatModePolicy scmPolicy =
-                mAppCompatController.getAppCompatSizeCompatModePolicy();
+                mAppCompatController.getSizeCompatModePolicy();
         return scmPolicy.replaceResolvedBoundsIfNeeded(resolvedBounds);
     }
 
@@ -8997,7 +8919,7 @@
         }
         final AppCompatDisplayInsets appCompatDisplayInsets = getAppCompatDisplayInsets();
         final AppCompatSizeCompatModePolicy scmPolicy =
-                mAppCompatController.getAppCompatSizeCompatModePolicy();
+                mAppCompatController.getSizeCompatModePolicy();
 
         if (appCompatDisplayInsets != null
                 && !appCompatDisplayInsets.mIsInFixedOrientationOrAspectRatioLetterbox) {
@@ -9048,8 +8970,10 @@
         final Rect prevResolvedBounds = new Rect(resolvedBounds);
         resolvedBounds.set(containingBounds);
 
-        mAppCompatController.getAppCompatAspectRatioPolicy()
-                .applyDesiredAspectRatio(newParentConfig, parentBounds, resolvedBounds,
+        final AppCompatAspectRatioPolicy aspectRatioPolicy = mAppCompatController
+                .getAspectRatioPolicy();
+
+        aspectRatioPolicy.applyDesiredAspectRatio(newParentConfig, parentBounds, resolvedBounds,
                         containingBoundsWithInsets, containingBounds);
 
         if (appCompatDisplayInsets != null) {
@@ -9078,8 +9002,8 @@
         // for comparison with size compat app bounds in {@link resolveSizeCompatModeConfiguration}.
         mResolveConfigHint.mTmpCompatInsets = appCompatDisplayInsets;
         computeConfigByResolveHint(getResolvedOverrideConfiguration(), newParentConfig);
-        mAppCompatController.getAppCompatAspectRatioPolicy()
-                .setLetterboxBoundsForFixedOrientationAndAspectRatio(new Rect(resolvedBounds));
+        aspectRatioPolicy.setLetterboxBoundsForFixedOrientationAndAspectRatio(
+                new Rect(resolvedBounds));
     }
 
     /**
@@ -9096,8 +9020,9 @@
         // Use tmp bounds to calculate aspect ratio so we can know whether the activity should use
         // restricted size (resolved bounds may be the requested override bounds).
         mTmpBounds.setEmpty();
-        mAppCompatController.getAppCompatAspectRatioPolicy()
-                .applyAspectRatioForLetterbox(mTmpBounds, parentAppBounds, parentBounds);
+        final AppCompatAspectRatioPolicy aspectRatioPolicy = mAppCompatController
+                .getAspectRatioPolicy();
+        aspectRatioPolicy.applyAspectRatioForLetterbox(mTmpBounds, parentAppBounds, parentBounds);
         // If the out bounds is not empty, it means the activity cannot fill parent's app bounds,
         // then they should be aligned later in #updateResolvedBoundsPosition()
         if (!mTmpBounds.isEmpty()) {
@@ -9108,8 +9033,7 @@
             // restrict, the bounds should be the requested override bounds.
             mResolveConfigHint.mTmpOverrideDisplayInfo = getFixedRotationTransformDisplayInfo();
             computeConfigByResolveHint(resolvedConfig, newParentConfiguration);
-            mAppCompatController.getAppCompatAspectRatioPolicy()
-                    .setLetterboxBoundsForAspectRatio(new Rect(resolvedBounds));
+            aspectRatioPolicy.setLetterboxBoundsForAspectRatio(new Rect(resolvedBounds));
         }
     }
 
@@ -9118,7 +9042,7 @@
         // TODO(b/268458693): Refactor configuration inheritance in case of translucent activities
         final Rect superBounds = super.getBounds();
         final AppCompatSizeCompatModePolicy scmPolicy =
-                mAppCompatController.getAppCompatSizeCompatModePolicy();
+                mAppCompatController.getSizeCompatModePolicy();
         return mAppCompatController.getTransparentPolicy().findOpaqueNotFinishingActivityBelow()
                 .map(ActivityRecord::getBounds)
                 .orElseGet(() -> scmPolicy.getAppSizeCompatBoundsIfAvailable(superBounds));
@@ -9359,18 +9283,11 @@
      * Returns the min aspect ratio of this activity.
      */
     float getMinAspectRatio() {
-        return mAppCompatController.getAppCompatAspectRatioPolicy().getMinAspectRatio();
+        return mAppCompatController.getAspectRatioPolicy().getMinAspectRatio();
     }
 
     float getMaxAspectRatio() {
-        return mAppCompatController.getAppCompatAspectRatioPolicy().getMaxAspectRatio();
-    }
-
-    /**
-     * Returns true if the activity has maximum or minimum aspect ratio.
-     */
-    private boolean hasFixedAspectRatio() {
-        return getMaxAspectRatio() != 0 || getMinAspectRatio() != 0;
+        return mAppCompatController.getAspectRatioPolicy().getMaxAspectRatio();
     }
 
     /**
@@ -9452,7 +9369,7 @@
         if (mVisibleRequested) {
             // Calling from here rather than resolveOverrideConfiguration to ensure that this is
             // called after full config is updated in ConfigurationContainer#onConfigurationChanged.
-            mAppCompatController.getAppCompatSizeCompatModePolicy().updateAppCompatDisplayInsets();
+            mAppCompatController.getSizeCompatModePolicy().updateAppCompatDisplayInsets();
         }
 
         // Short circuit: if the two full configurations are equal (the common case), then there is
@@ -9792,7 +9709,7 @@
 
         // Reset the existing override configuration so it can be updated according to the latest
         // configuration.
-        mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
+        mAppCompatController.getSizeCompatModePolicy().clearSizeCompatMode();
 
         if (!attachedToProcess()) {
             return;
@@ -10222,29 +10139,27 @@
         proto.write(LAST_DROP_INPUT_MODE, mLastDropInputMode);
         proto.write(OVERRIDE_ORIENTATION, getOverrideOrientation());
         proto.write(SHOULD_SEND_COMPAT_FAKE_FOCUS, shouldSendCompatFakeFocus());
+        final AppCompatCameraOverrides cameraOverrides =
+                mAppCompatController.getAppCompatCameraOverrides();
         proto.write(SHOULD_FORCE_ROTATE_FOR_CAMERA_COMPAT,
-                mAppCompatController.getAppCompatCameraOverrides()
-                        .shouldForceRotateForCameraCompat());
+                cameraOverrides.shouldForceRotateForCameraCompat());
         proto.write(SHOULD_REFRESH_ACTIVITY_FOR_CAMERA_COMPAT,
-                mAppCompatController.getAppCompatCameraOverrides()
-                        .shouldRefreshActivityForCameraCompat());
+                cameraOverrides.shouldRefreshActivityForCameraCompat());
         proto.write(SHOULD_REFRESH_ACTIVITY_VIA_PAUSE_FOR_CAMERA_COMPAT,
-                mAppCompatController.getAppCompatCameraOverrides()
-                        .shouldRefreshActivityViaPauseForCameraCompat());
+                cameraOverrides.shouldRefreshActivityViaPauseForCameraCompat());
+        final AppCompatAspectRatioOverrides aspectRatioOverrides =
+                mAppCompatController.getAspectRatioOverrides();
         proto.write(SHOULD_OVERRIDE_MIN_ASPECT_RATIO,
-                mAppCompatController.getAppCompatAspectRatioOverrides()
-                        .shouldOverrideMinAspectRatio());
+                aspectRatioOverrides.shouldOverrideMinAspectRatio());
         proto.write(SHOULD_IGNORE_ORIENTATION_REQUEST_LOOP,
                 mAppCompatController.getOrientationOverrides()
                         .shouldIgnoreOrientationRequestLoop());
         proto.write(SHOULD_OVERRIDE_FORCE_RESIZE_APP,
                 mAppCompatController.getResizeOverrides().shouldOverrideForceResizeApp());
         proto.write(SHOULD_ENABLE_USER_ASPECT_RATIO_SETTINGS,
-                mAppCompatController.getAppCompatAspectRatioOverrides()
-                        .shouldEnableUserAspectRatioSettings());
+                aspectRatioOverrides.shouldEnableUserAspectRatioSettings());
         proto.write(IS_USER_FULLSCREEN_OVERRIDE_ENABLED,
-                mAppCompatController.getAppCompatAspectRatioOverrides()
-                        .isUserFullscreenOverrideEnabled());
+                aspectRatioOverrides.isUserFullscreenOverrideEnabled());
     }
 
     @Override
@@ -10473,7 +10388,7 @@
      * game engines wait to get focus before drawing the content of the app.
      */
     boolean shouldSendCompatFakeFocus() {
-        return mAppCompatController.getAppCompatFocusOverrides().shouldSendFakeFocus();
+        return mAppCompatController.getFocusOverrides().shouldSendFakeFocus();
     }
 
     boolean canCaptureSnapshot() {
diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotController.java b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
index 26b7cc6..2162834 100644
--- a/services/core/java/com/android/server/wm/ActivitySnapshotController.java
+++ b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
@@ -100,19 +100,21 @@
 
     ActivitySnapshotController(WindowManagerService service, SnapshotPersistQueue persistQueue) {
         super(service);
-        mSnapshotPersistQueue = persistQueue;
-        mPersistInfoProvider = createPersistInfoProvider(service,
-                Environment::getDataSystemCeDirectory);
-        mPersister = new TaskSnapshotPersister(persistQueue, mPersistInfoProvider);
-        mSnapshotLoader = new AppSnapshotLoader(mPersistInfoProvider);
-        initialize(new ActivitySnapshotCache());
-
         final boolean snapshotEnabled =
                 !service.mContext
                         .getResources()
                         .getBoolean(com.android.internal.R.bool.config_disableTaskSnapshots)
                 && !ActivityManager.isLowRamDeviceStatic(); // Don't support Android Go
         setSnapshotEnabled(snapshotEnabled);
+        mSnapshotPersistQueue = persistQueue;
+        mPersistInfoProvider = createPersistInfoProvider(service,
+                Environment::getDataSystemCeDirectory);
+        mPersister = new TaskSnapshotPersister(
+                persistQueue,
+                mPersistInfoProvider,
+                shouldDisableSnapshots());
+        mSnapshotLoader = new AppSnapshotLoader(mPersistInfoProvider);
+        initialize(new ActivitySnapshotCache());
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 0ab2ffe..bdbd0d1 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1964,7 +1964,6 @@
             if (mLastStartActivityRecord != null) {
                 targetTaskTop.mLaunchSourceType = mLastStartActivityRecord.mLaunchSourceType;
             }
-            targetTaskTop.mTransitionController.collect(targetTaskTop);
             recordTransientLaunchIfNeeded(targetTaskTop);
             // Recycle the target task for this launch.
             startResult =
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 12c8f9c..906befc 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -1664,6 +1664,12 @@
         activityIdleInternal(null /* idleActivity */, false /* fromTimeout */,
                 true /* processPausingActivities */, null /* configuration */);
 
+        if (rootTask.getParent() == null) {
+            // The activities in the task may already be finishing. Then the task could be removed
+            // when performing the idle check.
+            return;
+        }
+
         // Reparent all the tasks to the bottom of the display
         final DisplayContent toDisplay =
                 mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY);
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
index 6a0de98..4ecd0be 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
@@ -76,7 +76,7 @@
     private float getDesiredAspectRatio(@NonNull Configuration newParentConfig,
             @NonNull Rect parentBounds) {
         final float letterboxAspectRatioOverride =
-                mAppCompatOverrides.getAppCompatAspectRatioOverrides()
+                mAppCompatOverrides.getAspectRatioOverrides()
                         .getFixedOrientationLetterboxAspectRatio(newParentConfig);
         // Aspect ratio as suggested by the system. Apps requested mix/max aspect ratio will
         // be respected in #applyAspectRatio.
@@ -127,7 +127,7 @@
         }
 
         final AppCompatAspectRatioOverrides aspectRatioOverrides =
-                mAppCompatOverrides.getAppCompatAspectRatioOverrides();
+                mAppCompatOverrides.getAspectRatioOverrides();
         if (aspectRatioOverrides.shouldApplyUserMinAspectRatioOverride()) {
             return aspectRatioOverrides.getUserMinAspectRatio();
         }
@@ -215,6 +215,13 @@
         mAppCompatAspectRatioState.mLetterboxBoundsForAspectRatio = bounds;
     }
 
+    /**
+     * Returns true if the activity has maximum or minimum aspect ratio.
+     */
+    boolean hasFixedAspectRatio() {
+        return getMaxAspectRatio() != 0 || getMinAspectRatio() != 0;
+    }
+
     private boolean isParentFullscreenPortrait() {
         final WindowContainer<?> parent = mActivityRecord.getParent();
         return parent != null
diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index 6d0e8ea..a94f625 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -31,11 +31,11 @@
     @NonNull
     private final AppCompatOrientationPolicy mOrientationPolicy;
     @NonNull
-    private final AppCompatAspectRatioPolicy mAppCompatAspectRatioPolicy;
+    private final AppCompatAspectRatioPolicy mAspectRatioPolicy;
     @NonNull
     private final AppCompatReachabilityPolicy mReachabilityPolicy;
     @NonNull
-    private final DesktopAppCompatAspectRatioPolicy mDesktopAppCompatAspectRatioPolicy;
+    private final DesktopAppCompatAspectRatioPolicy mDesktopAspectRatioPolicy;
     @NonNull
     private final AppCompatOverrides mAppCompatOverrides;
     @NonNull
@@ -43,7 +43,7 @@
     @NonNull
     private final AppCompatLetterboxPolicy mAppCompatLetterboxPolicy;
     @NonNull
-    private final AppCompatSizeCompatModePolicy mAppCompatSizeCompatModePolicy;
+    private final AppCompatSizeCompatModePolicy mSizeCompatModePolicy;
 
     AppCompatController(@NonNull WindowManagerService wmService,
                         @NonNull ActivityRecord activityRecord) {
@@ -56,15 +56,15 @@
         mAppCompatOverrides = new AppCompatOverrides(activityRecord, packageManager,
                 wmService.mAppCompatConfiguration, optPropBuilder, mAppCompatDeviceStateQuery);
         mOrientationPolicy = new AppCompatOrientationPolicy(activityRecord, mAppCompatOverrides);
-        mAppCompatAspectRatioPolicy = new AppCompatAspectRatioPolicy(activityRecord,
+        mAspectRatioPolicy = new AppCompatAspectRatioPolicy(activityRecord,
                 mTransparentPolicy, mAppCompatOverrides);
         mReachabilityPolicy = new AppCompatReachabilityPolicy(activityRecord,
                 wmService.mAppCompatConfiguration);
         mAppCompatLetterboxPolicy = new AppCompatLetterboxPolicy(activityRecord,
                 wmService.mAppCompatConfiguration);
-        mDesktopAppCompatAspectRatioPolicy = new DesktopAppCompatAspectRatioPolicy(activityRecord,
+        mDesktopAspectRatioPolicy = new DesktopAppCompatAspectRatioPolicy(activityRecord,
                 mAppCompatOverrides, mTransparentPolicy, wmService.mAppCompatConfiguration);
-        mAppCompatSizeCompatModePolicy = new AppCompatSizeCompatModePolicy(activityRecord,
+        mSizeCompatModePolicy = new AppCompatSizeCompatModePolicy(activityRecord,
                 mAppCompatOverrides);
     }
 
@@ -79,13 +79,13 @@
     }
 
     @NonNull
-    AppCompatAspectRatioPolicy getAppCompatAspectRatioPolicy() {
-        return mAppCompatAspectRatioPolicy;
+    AppCompatAspectRatioPolicy getAspectRatioPolicy() {
+        return mAspectRatioPolicy;
     }
 
     @NonNull
-    DesktopAppCompatAspectRatioPolicy getDesktopAppCompatAspectRatioPolicy() {
-        return mDesktopAppCompatAspectRatioPolicy;
+    DesktopAppCompatAspectRatioPolicy getDesktopAspectRatioPolicy() {
+        return mDesktopAspectRatioPolicy;
     }
 
     @NonNull
@@ -99,8 +99,8 @@
     }
 
     @NonNull
-    AppCompatAspectRatioOverrides getAppCompatAspectRatioOverrides() {
-        return mAppCompatOverrides.getAppCompatAspectRatioOverrides();
+    AppCompatAspectRatioOverrides getAspectRatioOverrides() {
+        return mAppCompatOverrides.getAspectRatioOverrides();
     }
 
     @NonNull
@@ -119,8 +119,8 @@
     }
 
     @NonNull
-    AppCompatFocusOverrides getAppCompatFocusOverrides() {
-        return mAppCompatOverrides.getAppCompatFocusOverrides();
+    AppCompatFocusOverrides getFocusOverrides() {
+        return mAppCompatOverrides.getFocusOverrides();
     }
 
     @NonNull
@@ -139,14 +139,14 @@
     }
 
     @NonNull
-    AppCompatSizeCompatModePolicy getAppCompatSizeCompatModePolicy() {
-        return mAppCompatSizeCompatModePolicy;
+    AppCompatSizeCompatModePolicy getSizeCompatModePolicy() {
+        return mSizeCompatModePolicy;
     }
 
     void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
         getTransparentPolicy().dump(pw, prefix);
         getAppCompatLetterboxPolicy().dump(pw, prefix);
-        getAppCompatSizeCompatModePolicy().dump(pw, prefix);
+        getSizeCompatModePolicy().dump(pw, prefix);
     }
 
 }
diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java b/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java
index af83668..a49bec0 100644
--- a/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatOrientationOverrides.java
@@ -144,7 +144,7 @@
         mOrientationOverridesState.updateOrientationRequestLoopState();
 
         return mOrientationOverridesState.shouldIgnoreRequestInLoop()
-                && !mActivityRecord.mAppCompatController.getAppCompatAspectRatioPolicy()
+                && !mActivityRecord.mAppCompatController.getAspectRatioPolicy()
                     .isLetterboxedForFixedOrientationAndAspectRatio();
     }
 
diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
index fc758ef..6202f80 100644
--- a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
@@ -55,7 +55,7 @@
     @ActivityInfo.ScreenOrientation
     int overrideOrientationIfNeeded(@ActivityInfo.ScreenOrientation int candidate) {
         final AppCompatAspectRatioOverrides aspectRatioOverrides =
-                mAppCompatOverrides.getAppCompatAspectRatioOverrides();
+                mAppCompatOverrides.getAspectRatioOverrides();
         // Ignore all orientation requests of activities for eligible virtual displays.
         if (aspectRatioOverrides.shouldIgnoreActivitySizeRestrictionsForDisplay()) {
             return SCREEN_ORIENTATION_USER;
diff --git a/services/core/java/com/android/server/wm/AppCompatOverrides.java b/services/core/java/com/android/server/wm/AppCompatOverrides.java
index 9fb54db..2d0ff9b 100644
--- a/services/core/java/com/android/server/wm/AppCompatOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatOverrides.java
@@ -31,9 +31,9 @@
     @NonNull
     private final AppCompatCameraOverrides mAppCompatCameraOverrides;
     @NonNull
-    private final AppCompatAspectRatioOverrides mAppCompatAspectRatioOverrides;
+    private final AppCompatAspectRatioOverrides mAspectRatioOverrides;
     @NonNull
-    private final AppCompatFocusOverrides mAppCompatFocusOverrides;
+    private final AppCompatFocusOverrides mFocusOverrides;
     @NonNull
     private final AppCompatResizeOverrides mResizeOverrides;
     @NonNull
@@ -52,11 +52,11 @@
                 appCompatConfiguration, optPropBuilder, mAppCompatCameraOverrides);
         mReachabilityOverrides = new AppCompatReachabilityOverrides(activityRecord,
                 appCompatConfiguration, appCompatDeviceStateQuery);
-        mAppCompatAspectRatioOverrides = new AppCompatAspectRatioOverrides(activityRecord,
+        mAspectRatioOverrides = new AppCompatAspectRatioOverrides(activityRecord,
                 appCompatConfiguration, optPropBuilder, appCompatDeviceStateQuery,
                 mReachabilityOverrides);
-        mAppCompatFocusOverrides = new AppCompatFocusOverrides(activityRecord,
-                appCompatConfiguration, optPropBuilder);
+        mFocusOverrides = new AppCompatFocusOverrides(activityRecord, appCompatConfiguration,
+                optPropBuilder);
         mResizeOverrides = new AppCompatResizeOverrides(activityRecord, packageManager,
                 optPropBuilder);
         mAppCompatLetterboxOverrides = new AppCompatLetterboxOverrides(activityRecord,
@@ -74,13 +74,13 @@
     }
 
     @NonNull
-    AppCompatAspectRatioOverrides getAppCompatAspectRatioOverrides() {
-        return mAppCompatAspectRatioOverrides;
+    AppCompatAspectRatioOverrides getAspectRatioOverrides() {
+        return mAspectRatioOverrides;
     }
 
     @NonNull
-    AppCompatFocusOverrides getAppCompatFocusOverrides() {
-        return mAppCompatFocusOverrides;
+    AppCompatFocusOverrides getFocusOverrides() {
+        return mFocusOverrides;
     }
 
     @NonNull
diff --git a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
index d278dc3..f48ef4f 100644
--- a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
@@ -18,6 +18,10 @@
 
 import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.content.pm.ActivityInfo.SIZE_CHANGES_SUPPORTED_METADATA;
+import static android.content.pm.ActivityInfo.SIZE_CHANGES_SUPPORTED_OVERRIDE;
+import static android.content.pm.ActivityInfo.SIZE_CHANGES_UNSUPPORTED_METADATA;
+import static android.content.pm.ActivityInfo.SIZE_CHANGES_UNSUPPORTED_OVERRIDE;
 import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
 
 import static com.android.server.wm.DesktopModeHelper.canEnterDesktopMode;
@@ -82,7 +86,8 @@
     }
 
     /**
-     * @return The {@code true} if the current instance has {@link #mAppCompatDisplayInsets} without
+     * @return The {@code true} if the current instance has
+     * {@link AppCompatSizeCompatModePolicy#mAppCompatDisplayInsets} without
      * considering the inheritance implemented in {@link #getAppCompatDisplayInsets()}
      */
     boolean hasAppCompatDisplayInsetsWithoutInheritance() {
@@ -199,7 +204,7 @@
         // activity will be displayed within them even if it is in size compat mode. They should be
         // saved here before resolved bounds are overridden below.
         final AppCompatAspectRatioPolicy aspectRatioPolicy = mActivityRecord.mAppCompatController
-                .getAppCompatAspectRatioPolicy();
+                .getAspectRatioPolicy();
         final boolean useResolvedBounds = aspectRatioPolicy.isAspectRatioApplied();
         final Rect containerBounds = useResolvedBounds
                 ? new Rect(resolvedBounds)
@@ -244,8 +249,7 @@
         resolvedBounds.set(containingBounds);
         // The size of floating task is fixed (only swap), so the aspect ratio is already correct.
         if (!appCompatDisplayInsets.mIsFloating) {
-            mActivityRecord.mAppCompatController.getAppCompatAspectRatioPolicy()
-                    .applyAspectRatioForLetterbox(resolvedBounds, containingAppBounds,
+            aspectRatioPolicy.applyAspectRatioForLetterbox(resolvedBounds, containingAppBounds,
                             containingBounds);
         }
 
@@ -359,7 +363,7 @@
         }
 
         final Rect letterboxedContainerBounds = mActivityRecord.mAppCompatController
-                .getAppCompatAspectRatioPolicy().getLetterboxedContainerBounds();
+                .getAspectRatioPolicy().getLetterboxedContainerBounds();
 
         // The role of AppCompatDisplayInsets is like the override bounds.
         mAppCompatDisplayInsets =
@@ -368,6 +372,112 @@
                             .mUseOverrideInsetsForConfig);
     }
 
+    /**
+     * @return {@code true} if this activity is in size compatibility mode that uses the different
+     *         density than its parent or its bounds don't fit in parent naturally.
+     */
+    boolean inSizeCompatMode() {
+        if (isInSizeCompatModeForBounds()) {
+            return true;
+        }
+        if (getAppCompatDisplayInsets() == null || !shouldCreateAppCompatDisplayInsets()
+                // The orientation is different from parent when transforming.
+                || mActivityRecord.isFixedRotationTransforming()) {
+            return false;
+        }
+        final Rect appBounds = mActivityRecord.getConfiguration().windowConfiguration
+                .getAppBounds();
+        if (appBounds == null) {
+            // The app bounds hasn't been computed yet.
+            return false;
+        }
+        final WindowContainer<?> parent = mActivityRecord.getParent();
+        if (parent == null) {
+            // The parent of detached Activity can be null.
+            return false;
+        }
+        final Configuration parentConfig = parent.getConfiguration();
+        // Although colorMode, screenLayout, smallestScreenWidthDp are also fixed, generally these
+        // fields should be changed with density and bounds, so here only compares the most
+        // significant field.
+        return parentConfig.densityDpi != mActivityRecord.getConfiguration().densityDpi;
+    }
+
+    /**
+     * Indicates the activity will keep the bounds and screen configuration when it was first
+     * launched, no matter how its parent changes.
+     *
+     * <p>If {@true}, then {@link AppCompatDisplayInsets} will be created in {@link
+     * ActivityRecord#resolveOverrideConfiguration} to "freeze" activity bounds and insets.
+     *
+     * @return {@code true} if this activity is declared as non-resizable and fixed orientation or
+     *         aspect ratio.
+     */
+    boolean shouldCreateAppCompatDisplayInsets() {
+        if (mActivityRecord.mAppCompatController.getAspectRatioOverrides()
+                .hasFullscreenOverride()) {
+            // If the user has forced the applications aspect ratio to be fullscreen, don't use size
+            // compatibility mode in any situation. The user has been warned and therefore accepts
+            // the risk of the application misbehaving.
+            return false;
+        }
+        switch (supportsSizeChanges()) {
+            case SIZE_CHANGES_SUPPORTED_METADATA:
+            case SIZE_CHANGES_SUPPORTED_OVERRIDE:
+                return false;
+            case SIZE_CHANGES_UNSUPPORTED_OVERRIDE:
+                return true;
+            default:
+                // Fall through
+        }
+        // Use root activity's info for tasks in multi-window mode, or fullscreen tasks in freeform
+        // task display areas, to ensure visual consistency across activity launches and exits in
+        // the same task.
+        final TaskDisplayArea tda = mActivityRecord.getTaskDisplayArea();
+        if (mActivityRecord.inMultiWindowMode() || (tda != null && tda.inFreeformWindowingMode())) {
+            final Task task = mActivityRecord.getTask();
+            final ActivityRecord root = task != null ? task.getRootActivity() : null;
+            if (root != null && root != mActivityRecord
+                    && !root.shouldCreateAppCompatDisplayInsets()) {
+                // If the root activity doesn't use size compatibility mode, the activities above
+                // are forced to be the same for consistent visual appearance.
+                return false;
+            }
+        }
+        final AppCompatAspectRatioPolicy aspectRatioPolicy = mActivityRecord.mAppCompatController
+                .getAspectRatioPolicy();
+        return !mActivityRecord.isResizeable() && (mActivityRecord.info.isFixedOrientation()
+                || aspectRatioPolicy.hasFixedAspectRatio())
+                // The configuration of non-standard type should be enforced by system.
+                // {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD} is set when this activity is
+                // added to a task, but this function is called when resolving the launch params, at
+                // which point, the activity type is still undefined if it will be standard.
+                // For other non-standard types, the type is set in the constructor, so this should
+                // not be a problem.
+                && mActivityRecord.isActivityTypeStandardOrUndefined();
+    }
+
+    /**
+     * Returns whether the activity supports size changes.
+     */
+    @ActivityInfo.SizeChangesSupportMode
+    int supportsSizeChanges() {
+        final AppCompatResizeOverrides resizeOverrides = mAppCompatOverrides.getResizeOverrides();
+        if (resizeOverrides.shouldOverrideForceNonResizeApp()) {
+            return SIZE_CHANGES_UNSUPPORTED_OVERRIDE;
+        }
+
+        if (mActivityRecord.info.supportsSizeChanges) {
+            return SIZE_CHANGES_SUPPORTED_METADATA;
+        }
+
+        if (resizeOverrides.shouldOverrideForceResizeApp()) {
+            return SIZE_CHANGES_SUPPORTED_OVERRIDE;
+        }
+
+        return SIZE_CHANGES_UNSUPPORTED_METADATA;
+    }
+
 
     private boolean isInSizeCompatModeForBounds(final @NonNull Rect appBounds,
             final @NonNull Rect containerBounds) {
diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java
index e28dddc..1ab0868 100644
--- a/services/core/java/com/android/server/wm/AppCompatUtils.java
+++ b/services/core/java/com/android/server/wm/AppCompatUtils.java
@@ -156,7 +156,7 @@
                 .getAppCompatLetterboxOverrides().isLetterboxEducationEnabled());
 
         final AppCompatAspectRatioOverrides aspectRatioOverrides =
-                top.mAppCompatController.getAppCompatAspectRatioOverrides();
+                top.mAppCompatController.getAspectRatioOverrides();
         appCompatTaskInfo.setUserFullscreenOverrideEnabled(
                 aspectRatioOverrides.shouldApplyUserFullscreenOverride());
         appCompatTaskInfo.setSystemFullscreenOverrideEnabled(
@@ -206,7 +206,7 @@
         appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode =
                 AppCompatCameraPolicy.getCameraCompatFreeformMode(top);
         appCompatTaskInfo.setHasMinAspectRatioOverride(top.mAppCompatController
-                .getDesktopAppCompatAspectRatioPolicy().hasMinAspectRatioOverride(task));
+                .getDesktopAspectRatioPolicy().hasMinAspectRatioOverride(task));
     }
 
     /**
@@ -222,7 +222,7 @@
             return "SIZE_COMPAT_MODE";
         }
         final AppCompatAspectRatioPolicy aspectRatioPolicy = activityRecord.mAppCompatController
-                .getAppCompatAspectRatioPolicy();
+                .getAspectRatioPolicy();
         if (aspectRatioPolicy.isLetterboxedForFixedOrientationAndAspectRatio()) {
             return "FIXED_ORIENTATION";
         }
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 37575f0..ab32e54 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -36,6 +36,7 @@
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK;
 import static com.android.server.wm.WindowContainer.SYNC_STATE_NONE;
 
+import android.annotation.BinderThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -44,6 +45,7 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Bundle;
+import android.os.IBinder;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.os.SystemProperties;
@@ -705,12 +707,34 @@
         private WindowState mNavigatingWindow;
         private RemoteCallback mObserver;
 
+        private final IBinder.DeathRecipient mListenerDeathRecipient =
+                new IBinder.DeathRecipient() {
+                    @Override
+                    @BinderThread
+                    public void binderDied() {
+                        synchronized (mWindowManagerService.mGlobalLock) {
+                            stopMonitorForRemote();
+                            stopMonitorTransition();
+                        }
+                    }
+                };
+
         void startMonitor(@NonNull WindowState window, @NonNull RemoteCallback observer) {
             mNavigatingWindow = window;
             mObserver = observer;
+            try {
+                mObserver.getInterface().asBinder().linkToDeath(mListenerDeathRecipient,
+                        0 /* flags */);
+            } catch (RemoteException r) {
+                Slog.e(TAG, "Failed to link to death");
+            }
         }
 
         void stopMonitorForRemote() {
+            if (mObserver != null) {
+                mObserver.getInterface().asBinder().unlinkToDeath(mListenerDeathRecipient,
+                        0 /* flags */);
+            }
             mObserver = null;
         }
 
diff --git a/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
index 43855aa..fee5566 100644
--- a/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
+++ b/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
@@ -189,7 +189,7 @@
 
         final ActivityInfo info = mActivityRecord.info;
         final AppCompatAspectRatioOverrides aspectRatioOverrides =
-                mAppCompatOverrides.getAppCompatAspectRatioOverrides();
+                mAppCompatOverrides.getAspectRatioOverrides();
         if (shouldApplyUserMinAspectRatioOverride(task)) {
             return aspectRatioOverrides.getUserMinAspectRatio();
         }
@@ -266,7 +266,7 @@
             return false;
         }
 
-        final int userAspectRatioCode = mAppCompatOverrides.getAppCompatAspectRatioOverrides()
+        final int userAspectRatioCode = mAppCompatOverrides.getAspectRatioOverrides()
                 .getUserMinAspectRatioOverrideCode();
 
         return userAspectRatioCode != USER_MIN_ASPECT_RATIO_UNSET
@@ -281,7 +281,7 @@
         // We use mBooleanPropertyAllowUserAspectRatioOverride to allow apps to opt-out which has
         // effect only if explicitly false. If mBooleanPropertyAllowUserAspectRatioOverride is null,
         // the current app doesn't opt-out so the first part of the predicate is true.
-        return mAppCompatOverrides.getAppCompatAspectRatioOverrides()
+        return mAppCompatOverrides.getAspectRatioOverrides()
                     .getAllowUserAspectRatioOverridePropertyValue()
                 && mAppCompatConfiguration.isUserAppAspectRatioSettingsEnabled()
                 && task.mDisplayContent.getIgnoreOrientationRequest();
diff --git a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
index fcf88d3..0106a64 100644
--- a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
+++ b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
@@ -104,7 +104,7 @@
         if (!DesktopModeFlags.ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue()) {
             return centerInScreen(idealSize, screenBounds);
         }
-        if (activity.mAppCompatController.getAppCompatAspectRatioOverrides()
+        if (activity.mAppCompatController.getAspectRatioOverrides()
                 .hasFullscreenOverride()) {
             // If the activity has a fullscreen override applied, it should be treated as
             // resizeable and match the device orientation. Thus the ideal size can be
@@ -112,7 +112,7 @@
             return centerInScreen(idealSize, screenBounds);
         }
         final DesktopAppCompatAspectRatioPolicy desktopAppCompatAspectRatioPolicy =
-                activity.mAppCompatController.getDesktopAppCompatAspectRatioPolicy();
+                activity.mAppCompatController.getDesktopAspectRatioPolicy();
         float appAspectRatio = desktopAppCompatAspectRatioPolicy.calculateAspectRatio(task);
         final float tdaWidth = stableBounds.width();
         final float tdaHeight = stableBounds.height();
@@ -190,7 +190,7 @@
             @NonNull ActivityRecord activity, @NonNull Task task) {
         final int activityOrientation = activity.getOverrideOrientation();
         final DesktopAppCompatAspectRatioPolicy desktopAppCompatAspectRatioPolicy =
-                activity.mAppCompatController.getDesktopAppCompatAspectRatioPolicy();
+                activity.mAppCompatController.getDesktopAspectRatioPolicy();
         if (desktopAppCompatAspectRatioPolicy.shouldApplyUserMinAspectRatioOverride(task)
                 && (!isFixedOrientation(activityOrientation)
                     || activityOrientation == SCREEN_ORIENTATION_LOCKED)) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index d32c31f..5435d8f 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -193,7 +193,6 @@
 import android.os.PowerManager;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
-import android.os.SystemClock;
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -2187,12 +2186,6 @@
         }
     }
 
-    /** Returns {@code true} if the screen rotation animation needs to wait for the window. */
-    boolean shouldSyncRotationChange(WindowState w) {
-        final AsyncRotationController controller = mAsyncRotationController;
-        return controller == null || !controller.isAsync(w);
-    }
-
     void notifyInsetsChanged(Consumer<WindowState> dispatchInsetsChanged) {
         if (mFixedRotationLaunchingApp != null) {
             // The insets state of fixed rotation app is a rotated copy. Make sure the visibilities
@@ -2279,10 +2272,6 @@
         if (!shellTransitions) {
             forAllWindows(w -> {
                 w.seamlesslyRotateIfAllowed(transaction, oldRotation, rotation, rotateSeamlessly);
-                if (!rotateSeamlessly && w.mHasSurface) {
-                    ProtoLog.v(WM_DEBUG_ORIENTATION, "Set mOrientationChanging of %s", w);
-                    w.setOrientationChanging(true);
-                }
             }, true /* traverseTopToBottom */);
             mPinnedTaskController.startSeamlessRotationIfNeeded(transaction, oldRotation, rotation);
             if (!mDisplayRotation.hasSeamlessRotatingWindow()) {
@@ -5083,15 +5072,6 @@
         Slog.w(TAG_WM, "Window freeze timeout expired.");
         mWmService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT;
 
-        forAllWindows(w -> {
-            if (!w.getOrientationChanging()) {
-                return;
-            }
-            w.orientationChangeTimedOut();
-            w.mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
-                    - mWmService.mDisplayFreezeTime);
-            Slog.w(TAG_WM, "Force clearing orientation change: " + w);
-        }, true /* traverseTopToBottom */);
         mWmService.mWindowPlacerLocked.performSurfacePlacement();
     }
 
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index db058ca..fa748d3 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -212,7 +212,7 @@
                     surface = null;
                     mDragState.mPid = callerPid;
                     mDragState.mUid = callerUid;
-                    mDragState.mOriginalAlpha = alpha;
+                    mDragState.mStartDragAlpha = alpha;
                     mDragState.mAnimatedScale = callingWin.mGlobalScale;
                     mDragState.mToken = dragToken;
                     mDragState.mStartDragDisplayContent = displayContent;
@@ -287,7 +287,7 @@
                 }
 
                 final SurfaceControl.Transaction transaction = mDragState.mTransaction;
-                transaction.setAlpha(surfaceControl, mDragState.mOriginalAlpha);
+                transaction.setAlpha(surfaceControl, mDragState.mStartDragAlpha);
                 transaction.show(surfaceControl);
                 displayContent.reparentToOverlay(transaction, surfaceControl);
                 mDragState.updateDragSurfaceLocked(true /* keepHandling */,
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index d48b9b4..69f32cb 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -82,6 +82,7 @@
 class DragState {
     private static final long MIN_ANIMATION_DURATION_MS = 195;
     private static final long MAX_ANIMATION_DURATION_MS = 375;
+    private static final float DIFFERENT_DISPLAY_RETURN_ANIMATION_SCALE = 0.75f;
 
     private static final int DRAG_FLAGS_URI_ACCESS = View.DRAG_FLAG_GLOBAL_URI_READ |
             View.DRAG_FLAG_GLOBAL_URI_WRITE;
@@ -114,8 +115,9 @@
     boolean mDragResult;
     boolean mRelinquishDragSurfaceToDropTarget;
     float mAnimatedScale = 1.0f;
-    float mOriginalAlpha;
-    float mOriginalDisplayX, mOriginalDisplayY;
+    float mStartDragAlpha;
+    // Coords are in display coordinates space.
+    float mStartDragDisplayX, mStartDragDisplayY;
     float mCurrentDisplayX, mCurrentDisplayY;
     float mThumbOffsetX, mThumbOffsetY;
     InputInterceptor mInputInterceptor;
@@ -497,8 +499,8 @@
      */
     void broadcastDragStartedLocked(final float touchX, final float touchY) {
         Trace.instant(TRACE_TAG_WINDOW_MANAGER, "DragDropController#DRAG_STARTED");
-        mOriginalDisplayX = mCurrentDisplayX = touchX;
-        mOriginalDisplayY = mCurrentDisplayY = touchY;
+        mStartDragDisplayX = mCurrentDisplayX = touchX;
+        mStartDragDisplayY = mCurrentDisplayY = touchY;
 
         // Cache a base-class instance of the clip metadata so that parceling
         // works correctly in calling out to the apps.
@@ -809,21 +811,32 @@
                             mCurrentDisplayY),
                     PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale,
                             mAnimatedScale),
-                    PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, 0f));
+                    PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mStartDragAlpha, 0f));
+            duration = MIN_ANIMATION_DURATION_MS;
+        } else if (Flags.enableConnectedDisplaysDnd() && mCurrentDisplayContent.getDisplayId()
+                != mStartDragDisplayContent.getDisplayId()) {
+            animator = ValueAnimator.ofPropertyValuesHolder(
+                    PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_X,
+                            mCurrentDisplayX - mThumbOffsetX, mCurrentDisplayX - mThumbOffsetX),
+                    PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_Y,
+                            mCurrentDisplayY - mThumbOffsetY, mCurrentDisplayY - mThumbOffsetY),
+                    PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale,
+                            DIFFERENT_DISPLAY_RETURN_ANIMATION_SCALE * mAnimatedScale),
+                    PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mStartDragAlpha, 0f));
             duration = MIN_ANIMATION_DURATION_MS;
         } else {
             animator = ValueAnimator.ofPropertyValuesHolder(
                     PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_X,
-                            mCurrentDisplayX - mThumbOffsetX, mOriginalDisplayX - mThumbOffsetX),
+                            mCurrentDisplayX - mThumbOffsetX, mStartDragDisplayX - mThumbOffsetX),
                     PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_Y,
-                            mCurrentDisplayY - mThumbOffsetY, mOriginalDisplayY - mThumbOffsetY),
+                            mCurrentDisplayY - mThumbOffsetY, mStartDragDisplayY - mThumbOffsetY),
                     PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale,
                             mAnimatedScale),
-                    PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha,
-                            mOriginalAlpha / 2));
+                    PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mStartDragAlpha,
+                            mStartDragAlpha / 2));
 
-            final float translateX = mOriginalDisplayX - mCurrentDisplayX;
-            final float translateY = mOriginalDisplayY - mCurrentDisplayY;
+            final float translateX = mStartDragDisplayX - mCurrentDisplayX;
+            final float translateY = mStartDragDisplayY - mCurrentDisplayY;
             // Adjust the duration to the travel distance.
             final double travelDistance = Math.sqrt(
                     translateX * translateX + translateY * translateY);
@@ -853,7 +866,7 @@
                             mCurrentDisplayY),
                     PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale,
                             mAnimatedScale),
-                    PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, 0f));
+                    PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mStartDragAlpha, 0f));
         } else {
             animator = ValueAnimator.ofPropertyValuesHolder(
                     PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_X,
@@ -861,7 +874,7 @@
                     PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_Y,
                             mCurrentDisplayY - mThumbOffsetY, mCurrentDisplayY),
                     PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale, 0),
-                    PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, 0));
+                    PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mStartDragAlpha, 0));
         }
 
         final AnimationListener listener = new AnimationListener();
diff --git a/services/core/java/com/android/server/wm/LaunchParamsController.java b/services/core/java/com/android/server/wm/LaunchParamsController.java
index d3c3d28..ba1401a 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsController.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsController.java
@@ -79,7 +79,8 @@
      * @param result    The resulting params.
      */
     void calculate(Task task, WindowLayout layout, ActivityRecord activity, ActivityRecord source,
-            ActivityOptions options, @Nullable Request request, int phase, LaunchParams result) {
+            ActivityOptions options, @Nullable Request request,
+            @LaunchParamsModifier.Phase int phase, LaunchParams result) {
         result.reset();
 
         if (task != null || activity != null) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 57fe0bb..7a3eb67 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -152,7 +152,6 @@
 import com.android.server.am.ActivityManagerService;
 import com.android.server.am.AppTimeTracker;
 import com.android.server.am.UserState;
-import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.policy.PermissionPolicyInternal;
 import com.android.server.policy.WindowManagerPolicy;
@@ -206,7 +205,6 @@
     // For seamless rotation cases this always stays true, as the windows complete their orientation
     // changes 1 by 1 without disturbing global state.
     boolean mOrientationChangeComplete = true;
-    boolean mWallpaperActionPending = false;
 
     private final Handler mHandler;
 
@@ -1101,10 +1099,6 @@
             }
         }
 
-        if ((bulkUpdateParams & SET_WALLPAPER_ACTION_PENDING) != 0) {
-            mWallpaperActionPending = true;
-        }
-
         return doRequest;
     }
 
@@ -1541,20 +1535,18 @@
         ActivityInfo aInfo = resolveHomeActivity(userId, homeIntent);
         boolean lookForSecondaryHomeActivityInPrimaryHomePackage = aInfo != null;
 
-        if (android.companion.virtual.flags.Flags.vdmCustomHome()) {
-            // Resolve the externally set home activity for this display, if any. If it is unset or
-            // we fail to resolve it, fallback to the default secondary home activity.
-            final ComponentName customHomeComponent =
-                    taskDisplayArea.getDisplayContent() != null
-                            ? taskDisplayArea.getDisplayContent().getCustomHomeComponent()
-                            : null;
-            if (customHomeComponent != null) {
-                homeIntent.setComponent(customHomeComponent);
-                ActivityInfo customHomeActivityInfo = resolveHomeActivity(userId, homeIntent);
-                if (customHomeActivityInfo != null) {
-                    aInfo = customHomeActivityInfo;
-                    lookForSecondaryHomeActivityInPrimaryHomePackage = false;
-                }
+        // Resolve the externally set home activity for this display, if any. If it is unset or
+        // we fail to resolve it, fallback to the default secondary home activity.
+        final ComponentName customHomeComponent =
+                taskDisplayArea.getDisplayContent() != null
+                        ? taskDisplayArea.getDisplayContent().getCustomHomeComponent()
+                        : null;
+        if (customHomeComponent != null) {
+            homeIntent.setComponent(customHomeComponent);
+            ActivityInfo customHomeActivityInfo = resolveHomeActivity(userId, homeIntent);
+            if (customHomeActivityInfo != null) {
+                aInfo = customHomeActivityInfo;
+                lookForSecondaryHomeActivityInPrimaryHomePackage = false;
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
index 3eb13c5..5e1d792 100644
--- a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
+++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
@@ -231,8 +231,13 @@
                             if (next.isReady(mUserManagerInternal)) {
                                 isReadyToWrite = true;
                                 next.onDequeuedLocked();
-                            } else {
+                            } else if (!mShutdown) {
                                 mWriteQueue.addLast(next);
+                            } else {
+                                // User manager is locked and device is shutting down, skip writing
+                                // this item.
+                                next.onDequeuedLocked();
+                                next = null;
                             }
                         }
                     }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index fe478c60..295759c 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -468,9 +468,6 @@
     // NOTE: This value needs to be persisted with each task
     private TaskDescription mTaskDescription;
 
-    /** @see #setCanAffectSystemUiFlags */
-    private boolean mCanAffectSystemUiFlags = true;
-
     private static Exception sTmpException;
 
     private boolean mForceShowForAllUsers;
@@ -3288,21 +3285,6 @@
         return isRootTask() && callback.test(this) ? this : null;
     }
 
-    /**
-     * @param canAffectSystemUiFlags If false, all windows in this task can not affect SystemUI
-     *                               flags. See {@link WindowState#canAffectSystemUiFlags()}.
-     */
-    void setCanAffectSystemUiFlags(boolean canAffectSystemUiFlags) {
-        mCanAffectSystemUiFlags = canAffectSystemUiFlags;
-    }
-
-    /**
-     * @see #setCanAffectSystemUiFlags
-     */
-    boolean canAffectSystemUiFlags() {
-        return mCanAffectSystemUiFlags;
-    }
-
     void dontAnimateDimExit() {
         mDimmer.dontAnimateExit();
     }
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index a031aca..1993053 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -409,6 +409,9 @@
 
     private boolean mForceTranslucent = false;
 
+    /** @see #setCanAffectSystemUiFlags */
+    private boolean mCanAffectSystemUiFlags = true;
+
     final Point mLastSurfaceSize = new Point();
 
     private final Rect mTmpBounds = new Rect();
@@ -967,6 +970,27 @@
     }
 
     /**
+     * @param canAffectSystemUiFlags If false, all windows in this taskfragment can not affect
+     *                               SystemUI flags. See
+     *                               {@link WindowState#canAffectSystemUiFlags()}.
+     */
+    void setCanAffectSystemUiFlags(boolean canAffectSystemUiFlags) {
+        mCanAffectSystemUiFlags = canAffectSystemUiFlags;
+    }
+
+    /**
+     * @see #setCanAffectSystemUiFlags
+     */
+    boolean canAffectSystemUiFlags() {
+        if (!mCanAffectSystemUiFlags) {
+            return false;
+        }
+        final TaskFragment parentTaskFragment =
+                getParent() != null ? getParent().asTaskFragment() : null;
+        return parentTaskFragment == null || parentTaskFragment.canAffectSystemUiFlags();
+    }
+
+    /**
      * Returns the TaskFragment that is being organized, which could be this or the ascendant
      * TaskFragment.
      */
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index c39671d..e3a5b66 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -96,9 +96,10 @@
     }
 
     @Override
+    @Result
     public int onCalculate(@Nullable Task task, @Nullable ActivityInfo.WindowLayout layout,
             @Nullable ActivityRecord activity, @Nullable ActivityRecord source,
-            @Nullable ActivityOptions options, @Nullable Request request, int phase,
+            @Nullable ActivityOptions options, @Nullable Request request, @Phase int phase,
             LaunchParams currentParams, LaunchParams outParams) {
         initLogBuilder(task, activity);
         final int result = calculate(task, layout, activity, source, options, request, phase,
@@ -107,9 +108,10 @@
         return result;
     }
 
+    @Result
     private int calculate(@Nullable Task task, @Nullable ActivityInfo.WindowLayout layout,
             @Nullable ActivityRecord activity, @Nullable ActivityRecord source,
-            @Nullable ActivityOptions options, @Nullable Request request, int phase,
+            @Nullable ActivityOptions options, @Nullable Request request, @Phase int phase,
             LaunchParams currentParams, LaunchParams outParams) {
         final ActivityRecord root;
         if (task != null) {
diff --git a/services/core/java/com/android/server/wm/TaskPersister.java b/services/core/java/com/android/server/wm/TaskPersister.java
index d89dc0b..91cd949 100644
--- a/services/core/java/com/android/server/wm/TaskPersister.java
+++ b/services/core/java/com/android/server/wm/TaskPersister.java
@@ -245,18 +245,20 @@
 
     private static String fileToString(File file) {
         final String newline = System.lineSeparator();
+        BufferedReader reader = null;
         try {
-            BufferedReader reader = new BufferedReader(new FileReader(file));
+            reader = new BufferedReader(new FileReader(file));
             StringBuffer sb = new StringBuffer((int) file.length() * 2);
             String line;
             while ((line = reader.readLine()) != null) {
                 sb.append(line + newline);
             }
-            reader.close();
             return sb.toString();
         } catch (IOException ioe) {
             Slog.e(TAG, "Couldn't read file " + file.getName());
             return null;
+        } finally {
+            IoUtils.closeQuietly(reader);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 7d300e98..432ed1d 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -66,16 +66,19 @@
 
     TaskSnapshotController(WindowManagerService service, SnapshotPersistQueue persistQueue) {
         super(service);
-        mPersistInfoProvider = createPersistInfoProvider(service,
-                Environment::getDataSystemCeDirectory);
-        mPersister = new TaskSnapshotPersister(persistQueue, mPersistInfoProvider);
-
-        initialize(new TaskSnapshotCache(new AppSnapshotLoader(mPersistInfoProvider)));
         final boolean snapshotEnabled =
                 !service.mContext
                         .getResources()
                         .getBoolean(com.android.internal.R.bool.config_disableTaskSnapshots);
         setSnapshotEnabled(snapshotEnabled);
+        mPersistInfoProvider = createPersistInfoProvider(service,
+                Environment::getDataSystemCeDirectory);
+
+        mPersister = new TaskSnapshotPersister(
+                persistQueue,
+                mPersistInfoProvider,
+                shouldDisableSnapshots());
+        initialize(new TaskSnapshotCache(new AppSnapshotLoader(mPersistInfoProvider)));
     }
 
     static PersistInfoProvider createPersistInfoProvider(WindowManagerService service,
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index 87be74a..538fd8d 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -26,6 +26,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.pm.UserManagerInternal;
+import com.android.window.flags.Flags;
 
 import java.io.File;
 import java.util.Arrays;
@@ -37,6 +38,8 @@
  */
 class TaskSnapshotPersister extends BaseAppSnapshotPersister {
 
+    private final boolean mDisableSnapshots;
+
     /**
      * The list of ids of the tasks that have been persisted since {@link #removeObsoleteFiles} was
      * called.
@@ -45,8 +48,10 @@
     private final ArraySet<Integer> mPersistedTaskIdsSinceLastRemoveObsolete = new ArraySet<>();
 
     TaskSnapshotPersister(SnapshotPersistQueue persistQueue,
-            PersistInfoProvider persistInfoProvider) {
+            PersistInfoProvider persistInfoProvider,
+            boolean disableSnapshots) {
         super(persistQueue, persistInfoProvider);
+        mDisableSnapshots = Flags.checkDisabledSnapshotsInTaskPersister() && disableSnapshots;
     }
 
     /**
@@ -57,6 +62,9 @@
      * @param snapshot The snapshot to persist.
      */
     void persistSnapshot(int taskId, int userId, TaskSnapshot snapshot) {
+        if (mDisableSnapshots) {
+            return;
+        }
         synchronized (mLock) {
             mPersistedTaskIdsSinceLastRemoveObsolete.add(taskId);
             super.persistSnapshot(taskId, userId, snapshot);
@@ -71,6 +79,9 @@
      */
     @Override
     void removeSnapshot(int taskId, int userId) {
+        if (mDisableSnapshots) {
+            return;
+        }
         synchronized (mLock) {
             mPersistedTaskIdsSinceLastRemoveObsolete.remove(taskId);
             super.removeSnapshot(taskId, userId);
@@ -86,7 +97,7 @@
      *                       model.
      */
     void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) {
-        if (runningUserIds.length == 0) {
+        if (runningUserIds.length == 0 || mDisableSnapshots) {
             return;
         }
         synchronized (mLock) {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index f4a455a..75cefdf 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -2890,6 +2890,13 @@
                     leashReference = leashReference.getParent();
                 }
             }
+            if (wc == leashReference
+                    && sortedTargets.get(i).mWindowingMode == WINDOWING_MODE_PINNED) {
+                // If a PiP task is the only target, we wanna make sure the transition root leash
+                // is at the top in case PiP is sent to back. This is done because a pinned task is
+                // meant to be always-on-top throughout a transition.
+                leashReference = ancestor.getTopChild();
+            }
             final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName(
                     "Transition Root: " + leashReference.getName())
                     .setCallsite("Transition.calculateTransitionRoots").build();
diff --git a/services/core/java/com/android/server/wm/TransparentPolicy.java b/services/core/java/com/android/server/wm/TransparentPolicy.java
index edd9924..88ea073 100644
--- a/services/core/java/com/android/server/wm/TransparentPolicy.java
+++ b/services/core/java/com/android/server/wm/TransparentPolicy.java
@@ -204,7 +204,7 @@
             return true;
         }
         final AppCompatSizeCompatModePolicy scmPolicy = mActivityRecord.mAppCompatController
-                .getAppCompatSizeCompatModePolicy();
+                .getSizeCompatModePolicy();
         if (mActivityRecord.getTask() == null || mActivityRecord.fillsParent()
                 || scmPolicy.hasAppCompatDisplayInsetsWithoutInheritance()) {
             return true;
diff --git a/services/core/java/com/android/server/wm/WindowManagerFlags.java b/services/core/java/com/android/server/wm/WindowManagerFlags.java
index 7ef8d8d..df70ed2 100644
--- a/services/core/java/com/android/server/wm/WindowManagerFlags.java
+++ b/services/core/java/com/android/server/wm/WindowManagerFlags.java
@@ -58,6 +58,8 @@
 
     final boolean mEnsureWallpaperInTransitions;
 
+    final boolean mAodTransition = Flags.aodTransition();
+
     /* End Available Flags */
 
     WindowManagerFlags() {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index e4ef3d1..bf23e75 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -331,6 +331,7 @@
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.os.TransferPipe;
 import com.android.internal.policy.IKeyguardDismissCallback;
@@ -600,6 +601,7 @@
     final boolean mLimitedAlphaCompositing;
     final int mMaxUiWidth;
 
+    @NonNull
     @VisibleForTesting
     WindowManagerPolicy mPolicy;
 
@@ -1054,13 +1056,16 @@
     private boolean mAnimationsDisabled = false;
     boolean mPointerLocationEnabled = false;
 
+    @NonNull
     final AppCompatConfiguration mAppCompatConfiguration;
 
     private boolean mIsIgnoreOrientationRequestDisabled;
 
+    @NonNull
     final InputManagerService mInputManager;
     final DisplayManagerInternal mDisplayManagerInternal;
     final DisplayManager mDisplayManager;
+    @NonNull
     final ActivityTaskManagerService mAtmService;
 
     /** Indicates whether this device supports wide color gamut / HDR rendering */
@@ -1116,7 +1121,9 @@
     static WindowManagerThreadPriorityBooster sThreadPriorityBooster =
             new WindowManagerThreadPriorityBooster();
 
+    @NonNull
     Supplier<SurfaceControl.Builder> mSurfaceControlFactory;
+    @NonNull
     Supplier<SurfaceControl.Transaction> mTransactionFactory;
 
     private final SurfaceControl.Transaction mTransaction;
@@ -1188,9 +1195,11 @@
 
     private volatile boolean mDisableSecureWindows = false;
 
-    public static WindowManagerService main(final Context context, final InputManagerService im,
-            final boolean showBootMsgs, WindowManagerPolicy policy,
-            ActivityTaskManagerService atm) {
+    /** Creates an instance of the WindowManagerService for the system server. */
+    public static WindowManagerService main(@NonNull final Context context,
+            @NonNull final InputManagerService im, final boolean showBootMsgs,
+            @NonNull final WindowManagerPolicy policy,
+            @NonNull final ActivityTaskManagerService atm) {
         // Using SysUI context to have access to Material colors extracted from Wallpaper.
         final AppCompatConfiguration appCompat = new AppCompatConfiguration(
                 ActivityThread.currentActivityThread().getSystemUiContext());
@@ -1204,15 +1213,19 @@
 
     /**
      * Creates and returns an instance of the WindowManagerService. This call allows the caller
-     * to override factories that can be used to stub native calls during test.
+     * to override factories that can be used to stub native calls during test. Tests should use
+     * {@link WindowManagerServiceTestSupport} instead of calling this directly to ensure
+     * proper initialization and cleanup of dependencies.
      */
-    @VisibleForTesting
-    public static WindowManagerService main(final Context context, final InputManagerService im,
-            final boolean showBootMsgs, WindowManagerPolicy policy, ActivityTaskManagerService atm,
-            DisplayWindowSettingsProvider displayWindowSettingsProvider,
-            Supplier<SurfaceControl.Transaction> transactionFactory,
-            Supplier<SurfaceControl.Builder> surfaceControlFactory,
-            AppCompatConfiguration appCompat) {
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static WindowManagerService main(@NonNull final Context context,
+            @NonNull final InputManagerService im, boolean showBootMsgs,
+            @NonNull final WindowManagerPolicy policy,
+            @NonNull final ActivityTaskManagerService atm,
+            @NonNull final DisplayWindowSettingsProvider displayWindowSettingsProvider,
+            @NonNull final Supplier<SurfaceControl.Transaction> transactionFactory,
+            @NonNull final Supplier<SurfaceControl.Builder> surfaceControlFactory,
+            @NonNull final AppCompatConfiguration appCompat) {
 
         final WindowManagerService[] wms = new WindowManagerService[1];
         DisplayThread.getHandler().runWithScissors(() ->
@@ -1238,12 +1251,13 @@
         new WindowManagerShellCommand(this).exec(this, in, out, err, args, callback, result);
     }
 
-    private WindowManagerService(Context context, InputManagerService inputManager,
-            boolean showBootMsgs, WindowManagerPolicy policy, ActivityTaskManagerService atm,
-            DisplayWindowSettingsProvider displayWindowSettingsProvider,
-            Supplier<SurfaceControl.Transaction> transactionFactory,
-            Supplier<SurfaceControl.Builder> surfaceControlFactory,
-            AppCompatConfiguration appCompat) {
+    private WindowManagerService(@NonNull Context context,
+            @NonNull InputManagerService inputManager, boolean showBootMsgs,
+            @NonNull WindowManagerPolicy policy, @NonNull ActivityTaskManagerService atm,
+            @NonNull DisplayWindowSettingsProvider displayWindowSettingsProvider,
+            @NonNull Supplier<SurfaceControl.Transaction> transactionFactory,
+            @NonNull Supplier<SurfaceControl.Builder> surfaceControlFactory,
+            @NonNull AppCompatConfiguration appCompat) {
         installLock(this, INDEX_WINDOW);
         mGlobalLock = atm.getGlobalLock();
         mAtmService = atm;
@@ -6393,11 +6407,6 @@
         if (mFrozenDisplayId != INVALID_DISPLAY && mFrozenDisplayId == w.getDisplayId()
                 && mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_TIMEOUT) {
             ProtoLog.v(WM_DEBUG_ORIENTATION, "Changing surface while display frozen: %s", w);
-            // WindowsState#reportResized won't tell invisible requested window to redraw,
-            // so do not set it as changing orientation to avoid affecting draw state.
-            if (w.isVisibleRequested()) {
-                w.setOrientationChanging(true);
-            }
             if (mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_NONE) {
                 mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
                 // XXX should probably keep timeout from
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index f0f1b2e..3c3a180 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -38,6 +38,7 @@
 import static android.window.TaskFragmentOperation.OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_COMPANION_TASK_FRAGMENT;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_DECOR_SURFACE_BOOSTED;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_DIM_ON_TASK;
@@ -1862,6 +1863,13 @@
                 taskFragment.setPinned(pinned);
                 break;
             }
+            case OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS: {
+                taskFragment.setCanAffectSystemUiFlags(operation.getBooleanValue());
+
+                // Request to apply the flags.
+                mService.mWindowManager.mWindowPlacerLocked.requestTraversal();
+                break;
+            }
         }
         return effects;
     }
@@ -1937,6 +1945,16 @@
             return false;
         }
 
+        if ((opType == OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS)
+                && !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) {
+            final Throwable exception = new SecurityException(
+                    "Only a system organizer can perform OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS."
+            );
+            sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment,
+                    opType, exception);
+            return false;
+        }
+
         final IBinder secondaryFragmentToken = operation.getSecondaryFragmentToken();
         return secondaryFragmentToken == null
                 || validateTaskFragment(mLaunchTaskFragments.get(secondaryFragmentToken), opType,
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 85e3d89..da58470 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -148,7 +148,6 @@
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_REMOVING_FOCUS;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
-import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_TIMEOUT;
 import static com.android.server.wm.WindowStateAnimator.COMMIT_DRAW_PENDING;
 import static com.android.server.wm.WindowStateAnimator.DRAW_PENDING;
 import static com.android.server.wm.WindowStateAnimator.HAS_DRAWN;
@@ -592,27 +591,10 @@
     /** Completely remove from window manager after exit animation? */
     boolean mRemoveOnExit;
 
-    /**
-     * Set when the orientation is changing and this window has not yet
-     * been updated for the new orientation.
-     */
-    private boolean mOrientationChanging;
-
     /** The time when the window was last requested to redraw for orientation change. */
     private long mOrientationChangeRedrawRequestTime;
 
     /**
-     * Sometimes in addition to the mOrientationChanging
-     * flag we report that the orientation is changing
-     * due to a mismatch in current and reported configuration.
-     *
-     * In the case of timeout we still need to make sure we
-     * leave the orientation changing state though, so we
-     * use this as a special time out escape hatch.
-     */
-    private boolean mOrientationChangeTimedOut;
-
-    /**
      * The orientation during the last visible call to relayout. If our
      * current orientation is different, the window can't be ready
      * to be shown.
@@ -1497,8 +1479,7 @@
 
             // Reset the drawn state if the window need to redraw for the change, so the transition
             // can wait until it has finished drawing to start.
-            if ((configChanged || getOrientationChanging() || dragResizingChanged)
-                    && isVisibleRequested()) {
+            if ((configChanged || dragResizingChanged) && isVisibleRequested()) {
                 winAnimator.mDrawState = DRAW_PENDING;
                 if (mActivityRecord != null) {
                     mActivityRecord.clearAllDrawn();
@@ -1512,15 +1493,6 @@
                 ProtoLog.v(WM_DEBUG_RESIZE, "Resizing window %s", this);
                 mWmService.mResizingWindows.add(this);
             }
-        } else if (getOrientationChanging()) {
-            if (isDrawn()) {
-                ProtoLog.v(WM_DEBUG_ORIENTATION,
-                        "Orientation not waiting for draw in %s, surfaceController %s", this,
-                        winAnimator.mSurfaceControl);
-                setOrientationChanging(false);
-                mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
-                        - mWmService.mDisplayFreezeTime);
-            }
         }
     }
 
@@ -1528,46 +1500,6 @@
         return !mWindowFrames.mFrame.equals(mWindowFrames.mLastFrame);
     }
 
-    boolean getOrientationChanging() {
-        if (mTransitionController.isShellTransitionsEnabled()) {
-            // Shell transition doesn't use the methods for display frozen state.
-            return false;
-        }
-        // In addition to the local state flag, we must also consider the difference in the last
-        // reported configuration vs. the current state. If the client code has not been informed of
-        // the change, logic dependent on having finished processing the orientation, such as
-        // unfreezing, could be improperly triggered.
-        // TODO(b/62846907): Checking against {@link mLastReportedConfiguration} could be flaky as
-        //                   this is not necessarily what the client has processed yet. Find a
-        //                   better indicator consistent with the client.
-        return (mOrientationChanging || (isVisible()
-                && getConfiguration().orientation != getLastReportedConfiguration().orientation))
-                && !mSeamlesslyRotated
-                && !mOrientationChangeTimedOut;
-    }
-
-    void setOrientationChanging(boolean changing) {
-        mOrientationChangeTimedOut = false;
-        if (mOrientationChanging == changing) {
-            return;
-        }
-        mOrientationChanging = changing;
-        if (changing) {
-            mLastFreezeDuration = 0;
-            if (mWmService.mRoot.mOrientationChangeComplete
-                    && mDisplayContent.shouldSyncRotationChange(this)) {
-                mWmService.mRoot.mOrientationChangeComplete = false;
-            }
-        } else {
-            // The orientation change is completed. If it was hidden by the animation, reshow it.
-            mDisplayContent.finishAsyncRotation(mToken);
-        }
-    }
-
-    void orientationChangeTimedOut() {
-        mOrientationChangeTimedOut = true;
-    }
-
     @Override
     void onDisplayChanged(DisplayContent dc) {
         if (dc != null && mDisplayContent != null && dc != mDisplayContent
@@ -3355,12 +3287,6 @@
 
         mAppFreezing = false;
 
-        if (mHasSurface && !getOrientationChanging()
-                && mWmService.mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_TIMEOUT) {
-            ProtoLog.v(WM_DEBUG_ORIENTATION,
-                    "set mOrientationChanging of %s", this);
-            setOrientationChanging(true);
-        }
         mLastFreezeDuration = 0;
         setDisplayLayoutNeeded();
         return true;
@@ -4266,9 +4192,8 @@
                     + " mDestroying=" + mDestroying
                     + " mRemoved=" + mRemoved);
         }
-        if (getOrientationChanging() || mAppFreezing) {
-            pw.println(prefix + "mOrientationChanging=" + mOrientationChanging
-                    + " configOrientationChanging="
+        if (mAppFreezing) {
+            pw.println(prefix + " configOrientationChanging="
                     + (getLastReportedConfiguration().orientation != getConfiguration().orientation)
                     + " mAppFreezing=" + mAppFreezing);
         }
@@ -5356,7 +5281,7 @@
             // Send information to SurfaceFlinger about the priority of the current window.
             updateFrameRateSelectionPriorityIfNeeded();
             updateScaleIfNeeded();
-            mWinAnimator.prepareSurfaceLocked(getSyncTransaction());
+            mWinAnimator.prepareSurfaceLocked(getPendingTransaction());
             applyDims();
         }
         super.prepareSurfaces();
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 0154d95..298580e 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -29,7 +29,6 @@
 
 import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ANIM;
 import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_DRAW;
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ORIENTATION;
 import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_STARTING_WINDOW;
 import static com.android.internal.protolog.WmProtoLogGroups.WM_SHOW_SURFACE_ALLOC;
 import static com.android.internal.protolog.WmProtoLogGroups.WM_SHOW_TRANSACTIONS;
@@ -417,56 +416,19 @@
         }
     }
 
-    void computeShownFrameLocked() {
-        if (mWin.mIsWallpaper && mService.mRoot.mWallpaperActionPending) {
-            return;
-        } else if (mWin.isDragResizeChanged()) {
-            // This window is awaiting a relayout because user just started (or ended)
-            // drag-resizing. The shown frame (which affects surface size and pos)
-            // should not be updated until we get next finished draw with the new surface.
-            // Otherwise one or two frames rendered with old settings would be displayed
-            // with new geometry.
-            return;
-        }
-
-        if (DEBUG) {
-            Slog.v(TAG, "computeShownFrameLocked: " + this
-                    + " not attached, mAlpha=" + mAlpha);
-        }
-
-        mShownAlpha = mAlpha;
-    }
-
     void prepareSurfaceLocked(SurfaceControl.Transaction t) {
         final WindowState w = mWin;
         if (!hasSurface()) {
-
-            // There is no need to wait for an animation change if our window is gone for layout
-            // already as we'll never be visible.
-            if (w.getOrientationChanging() && w.isGoneForLayout()) {
-                ProtoLog.v(WM_DEBUG_ORIENTATION, "Orientation change skips hidden %s", w);
-                w.setOrientationChanging(false);
-            }
             return;
         }
 
-        computeShownFrameLocked();
+        mShownAlpha = mAlpha;
 
         if (!w.isOnScreen()) {
             hide(t, "prepareSurfaceLocked");
             if (!w.mIsWallpaper || !mService.mFlags.mEnsureWallpaperInTransitions) {
                 mWallpaperControllerLocked.hideWallpapers(w);
             }
-
-            // If we are waiting for this window to handle an orientation change. If this window is
-            // really hidden (gone for layout), there is no point in still waiting for it.
-            // Note that this does introduce a potential glitch if the window becomes unhidden
-            // before it has drawn for the new orientation.
-            if (w.getOrientationChanging() && w.isGoneForLayout()) {
-                w.setOrientationChanging(false);
-                ProtoLog.v(WM_DEBUG_ORIENTATION,
-                        "Orientation change skips hidden %s", w);
-            }
         } else if (mLastAlpha != mShownAlpha
                 || mLastHidden) {
             mLastAlpha = mShownAlpha;
@@ -480,35 +442,9 @@
                 if (mLastHidden) {
                     showRobustly(t);
                     mLastHidden = false;
-                    final DisplayContent displayContent = w.getDisplayContent();
-                    if (!displayContent.getLastHasContent()) {
-                        // This draw means the difference between unique content and mirroring.
-                        // Run another pass through performLayout to set mHasContent in the
-                        // LogicalDisplay.
-                        displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_ANIM;
-                        if (DEBUG_LAYOUT_REPEATS) {
-                            mService.mWindowPlacerLocked.debugLayoutRepeats(
-                                    "showSurfaceRobustlyLocked " + w,
-                                    displayContent.pendingLayoutChanges);
-                        }
-                    }
                 }
             }
         }
-
-        if (w.getOrientationChanging()) {
-            if (!w.isDrawn()) {
-                if (w.mDisplayContent.shouldSyncRotationChange(w)) {
-                    w.mWmService.mRoot.mOrientationChangeComplete = false;
-                    mAnimator.mLastWindowFreezeSource = w;
-                }
-                ProtoLog.v(WM_DEBUG_ORIENTATION,
-                        "Orientation continue waiting for draw in %s", w);
-            } else {
-                w.setOrientationChanging(false);
-                ProtoLog.v(WM_DEBUG_ORIENTATION, "Orientation change complete in %s", w);
-            }
-        }
     }
 
     private void showRobustly(SurfaceControl.Transaction t) {
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index dff718a..a34b511 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -127,7 +127,6 @@
             mService.mAnimationHandler.removeCallbacks(mPerformSurfacePlacement);
             loopCount--;
         } while (mTraversalScheduled && loopCount > 0);
-        mService.mRoot.mWallpaperActionPending = false;
     }
 
     private void performSurfacePlacementLoop() {
diff --git a/services/core/xsd/device-state-config/device-state-config.xsd b/services/core/xsd/device-state-config/device-state-config.xsd
index 4a94732..324c9b4 100644
--- a/services/core/xsd/device-state-config/device-state-config.xsd
+++ b/services/core/xsd/device-state-config/device-state-config.xsd
@@ -41,6 +41,7 @@
                 <xs:annotation name="nullable" />
             </xs:element>
             <xs:element name="properties" type="properties" />
+            <xs:element name="flags" type="flags" />
             <xs:element name="conditions" type="conditions" />
         </xs:sequence>
     </xs:complexType>
@@ -53,6 +54,14 @@
         </xs:sequence>
     </xs:complexType>
 
+    <xs:complexType name="flags">
+        <xs:sequence>
+            <xs:element name="flag" type="xs:string" minOccurs="0" maxOccurs="unbounded">
+                <xs:annotation name="nullable" />
+            </xs:element>
+        </xs:sequence>
+    </xs:complexType>
+
     <xs:complexType name="conditions">
         <xs:sequence>
             <xs:element name="lid-switch" type="lidSwitchCondition" minOccurs="0">
diff --git a/services/core/xsd/device-state-config/schema/current.txt b/services/core/xsd/device-state-config/schema/current.txt
index 5bb216e..c94f3a8 100644
--- a/services/core/xsd/device-state-config/schema/current.txt
+++ b/services/core/xsd/device-state-config/schema/current.txt
@@ -11,10 +11,12 @@
   public class DeviceState {
     ctor public DeviceState();
     method public com.android.server.policy.devicestate.config.Conditions getConditions();
+    method public com.android.server.policy.devicestate.config.Flags getFlags();
     method public java.math.BigInteger getIdentifier();
     method @Nullable public String getName();
     method public com.android.server.policy.devicestate.config.Properties getProperties();
     method public void setConditions(com.android.server.policy.devicestate.config.Conditions);
+    method public void setFlags(com.android.server.policy.devicestate.config.Flags);
     method public void setIdentifier(java.math.BigInteger);
     method public void setName(@Nullable String);
     method public void setProperties(com.android.server.policy.devicestate.config.Properties);
@@ -25,6 +27,11 @@
     method public java.util.List<com.android.server.policy.devicestate.config.DeviceState> getDeviceState();
   }
 
+  public class Flags {
+    ctor public Flags();
+    method @Nullable public java.util.List<java.lang.String> getFlag();
+  }
+
   public class LidSwitchCondition {
     ctor public LidSwitchCondition();
     method public boolean getOpen();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 0ce25db..e960abd 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -16412,7 +16412,7 @@
 
     @Override
     public android.app.admin.EnforcingAdmin getEnforcingAdmin(int userId, String identifier) {
-        Preconditions.checkCallAuthorization(isSystemUid(getCallerIdentity()));
+        Preconditions.checkCallAuthorization(canQueryAdminPolicy(getCallerIdentity()));
         return getEnforcingAdminInternal(userId, identifier);
     }
 
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java
index ce4126a..55d23585 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java
@@ -34,8 +34,10 @@
 import android.hardware.SensorManager;
 import android.hardware.display.DisplayManager;
 import android.os.Handler;
+import android.os.PowerManager;
 import android.util.ArraySet;
 import android.util.Dumpable;
+import android.util.Slog;
 import android.view.Display;
 import android.view.DisplayInfo;
 import android.view.Surface;
@@ -44,6 +46,7 @@
 import com.android.server.policy.BookStylePreferredScreenCalculator.HingeAngle;
 import com.android.server.policy.BookStylePreferredScreenCalculator.StateTransition;
 import com.android.server.policy.BookStyleClosedStatePredicate.ConditionSensorListener.SensorSubscription;
+import com.android.server.policy.feature.flags.FeatureFlags;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -62,12 +65,19 @@
 public class BookStyleClosedStatePredicate implements Predicate<FoldableDeviceStateProvider>,
         DisplayManager.DisplayListener, Dumpable {
 
+    private static final String TAG = "BookStyleClosedStatePredicate";
+
     private final BookStylePreferredScreenCalculator mClosedStateCalculator;
     private final Handler mHandler = new Handler();
     private final PostureEstimator mPostureEstimator;
     private final DisplayManager mDisplayManager;
+    private final PowerManager mPowerManager;
+    private final FeatureFlags mFeatureFlags;
     private final DisplayInfo mDefaultDisplayInfo = new DisplayInfo();
 
+    @PowerManager.ScreenTimeoutPolicy
+    private volatile int mScreenTimeoutPolicy;
+
     /**
      * Creates {@link BookStyleClosedStatePredicate}. It is expected that the device has a pair
      * of accelerometer sensors (one for each movable part of the device), see parameter
@@ -92,8 +102,11 @@
     public BookStyleClosedStatePredicate(@NonNull Context context,
             @NonNull ClosedStateUpdatesListener updatesListener,
             @Nullable Sensor leftAccelerometerSensor, @Nullable Sensor rightAccelerometerSensor,
-            @NonNull List<StateTransition> stateTransitions) {
+            @NonNull List<StateTransition> stateTransitions,
+            @NonNull FeatureFlags featureFlags) {
+        mFeatureFlags = featureFlags;
         mDisplayManager = context.getSystemService(DisplayManager.class);
+        mPowerManager = context.getSystemService(PowerManager.class);
         mDisplayManager.registerDisplayListener(this, mHandler);
 
         mClosedStateCalculator = new BookStylePreferredScreenCalculator(stateTransitions);
@@ -108,6 +121,23 @@
     }
 
     /**
+     * Initialize the predicate, the predicate could subscribe to various data sources the data
+     * from which could be used later when calling {@link BookStyleClosedStatePredicate#test}.
+     */
+    public void init() {
+        if (mFeatureFlags.forceFoldablesTentModeWithScreenWakelock()) {
+            try {
+                mPowerManager.addScreenTimeoutPolicyListener(DEFAULT_DISPLAY, Runnable::run,
+                        new ScreenTimeoutPolicyListener());
+            } catch (IllegalStateException exception) {
+                // TODO: b/389613319 - remove after removing the screen timeout policy API flagging
+                Slog.e(TAG, "Error subscribing to the screen timeout policy changes");
+                exception.printStackTrace();
+            }
+        }
+    }
+
+    /**
      * Based on the current sensor readings and current state, returns true if the device should use
      * 'CLOSED' device state and false if it should not use 'CLOSED' state (e.g. could use half-open
      * or open states).
@@ -119,13 +149,24 @@
 
         mPostureEstimator.onDeviceClosedStatusChanged(hingeAngle == ANGLE_0);
 
+        final boolean isLikelyTentOrWedgeMode = mPostureEstimator.isLikelyTentOrWedgeMode()
+                || shouldForceTentOrWedgeMode();
+
         final PreferredScreen preferredScreen = mClosedStateCalculator.
-                calculatePreferredScreen(hingeAngle, mPostureEstimator.isLikelyTentOrWedgeMode(),
+                calculatePreferredScreen(hingeAngle, isLikelyTentOrWedgeMode,
                         mPostureEstimator.isLikelyReverseWedgeMode(hingeAngle));
 
         return preferredScreen == OUTER;
     }
 
+    private boolean shouldForceTentOrWedgeMode() {
+        if (!mFeatureFlags.forceFoldablesTentModeWithScreenWakelock()) {
+            return false;
+        }
+
+        return mScreenTimeoutPolicy == PowerManager.SCREEN_TIMEOUT_KEEP_DISPLAY_ON;
+    }
+
     private HingeAngle hingeAngleFromFloat(float hingeAngle) {
         if (hingeAngle == 0f) {
             return ANGLE_0;
@@ -163,7 +204,7 @@
     @Override
     public void dump(@NonNull PrintWriter writer, @Nullable String[] args) {
         writer.println("  " + getDumpableName());
-
+        writer.println("  mScreenTimeoutPolicy=" + mScreenTimeoutPolicy);
         mPostureEstimator.dump(writer, args);
         mClosedStateCalculator.dump(writer, args);
     }
@@ -172,6 +213,15 @@
         void onClosedStateUpdated();
     }
 
+    private class ScreenTimeoutPolicyListener implements
+            PowerManager.ScreenTimeoutPolicyListener {
+        @Override
+        public void onScreenTimeoutPolicyChanged(int screenTimeoutPolicy) {
+            // called from the binder thread
+            mScreenTimeoutPolicy = screenTimeoutPolicy;
+        }
+    }
+
     /**
      * Estimates if the device is going to enter wedge/tent mode based on the sensor data
      */
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
index f34ec72..dfc4ba2 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
@@ -114,7 +114,7 @@
         mIsDualDisplayBlockingEnabled = featureFlags.enableDualDisplayBlocking();
 
         final DeviceStatePredicateWrapper[] configuration = createConfiguration(
-                leftAccelerometerSensor, rightAccelerometerSensor, closeAngleDegrees);
+                leftAccelerometerSensor, rightAccelerometerSensor, closeAngleDegrees, featureFlags);
 
         mProvider = new FoldableDeviceStateProvider(mContext, sensorManager, hingeAngleSensor,
                 hallSensor, displayManager, configuration);
@@ -122,10 +122,10 @@
 
     private DeviceStatePredicateWrapper[] createConfiguration(
             @Nullable Sensor leftAccelerometerSensor, @Nullable Sensor rightAccelerometerSensor,
-            Integer closeAngleDegrees) {
+            Integer closeAngleDegrees, @NonNull FeatureFlags featureFlags) {
         return new DeviceStatePredicateWrapper[]{
                 createClosedConfiguration(leftAccelerometerSensor, rightAccelerometerSensor,
-                        closeAngleDegrees),
+                        closeAngleDegrees, featureFlags),
                 createConfig(getHalfOpenedDeviceState(), /* activeStatePredicate= */
                         (provider) -> {
                             final float hingeAngle = provider.getHingeAngle();
@@ -147,7 +147,7 @@
 
     private DeviceStatePredicateWrapper createClosedConfiguration(
             @Nullable Sensor leftAccelerometerSensor, @Nullable Sensor rightAccelerometerSensor,
-            @Nullable Integer closeAngleDegrees) {
+            @Nullable Integer closeAngleDegrees, @NonNull FeatureFlags featureFlags) {
 
         if (closeAngleDegrees != null) {
             // Switch displays at closeAngleDegrees in both ways (folding and unfolding)
@@ -161,9 +161,12 @@
         if (mEnablePostureBasedClosedState) {
             // Use smart closed state predicate that will use different switch angles
             // based on the device posture (e.g. wedge mode, tent mode, reverse wedge mode)
-            return createConfig(getClosedDeviceState(), /* activeStatePredicate= */
-                    new BookStyleClosedStatePredicate(mContext, this, leftAccelerometerSensor,
-                            rightAccelerometerSensor, DEFAULT_STATE_TRANSITIONS));
+            final BookStyleClosedStatePredicate predicate = new BookStyleClosedStatePredicate(
+                    mContext, this, leftAccelerometerSensor, rightAccelerometerSensor,
+                    DEFAULT_STATE_TRANSITIONS, featureFlags);
+            return createConfig(getClosedDeviceState(),
+                    /* activeStatePredicate= */ predicate,
+                    /* initializer= */ predicate::init);
         }
 
         // Switch to the outer display only at 0 degrees but use TENT_MODE_SWITCH_ANGLE_DEGREES
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
index daeaa98..8e74952 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
@@ -200,6 +200,16 @@
         }
     }
 
+    @Override
+    public void onSystemReady() {
+        for (int i = 0; i < mConfigurations.length; i++) {
+            final DeviceStatePredicateWrapper configuration = mConfigurations[i];
+            if (configuration.mInitializer != null) {
+                configuration.mInitializer.run();
+            }
+        }
+    }
+
     private void assertUniqueDeviceStateIdentifier(DeviceStatePredicateWrapper configuration) {
         if (mStateConditions.get(configuration.mDeviceState.getIdentifier()) != null) {
             throw new IllegalArgumentException("Device state configurations must have unique"
@@ -461,11 +471,12 @@
         private final DeviceState mDeviceState;
         private final Predicate<FoldableDeviceStateProvider> mActiveStatePredicate;
         private final Predicate<FoldableDeviceStateProvider> mAvailabilityPredicate;
+        private final Runnable mInitializer;
 
         private DeviceStatePredicateWrapper(
                 @NonNull DeviceState deviceState,
                 @NonNull Predicate<FoldableDeviceStateProvider> predicate) {
-            this(deviceState, predicate, ALLOWED);
+            this(deviceState, predicate, ALLOWED, /* initializer= */ null);
         }
 
         /** Create a configuration with availability and availability predicate **/
@@ -473,10 +484,28 @@
                 @NonNull DeviceState deviceState,
                 @NonNull Predicate<FoldableDeviceStateProvider> activeStatePredicate,
                 @NonNull Predicate<FoldableDeviceStateProvider> availabilityPredicate) {
+            this(deviceState, activeStatePredicate, availabilityPredicate, /* initializer= */ null);
+        }
+
+        /**
+         * Create a configuration with availability and availability predicate.
+         * @param deviceState specifies device state for this configuration
+         * @param activeStatePredicate predicate that should return 'true' when this device state
+         *                             wants to be and can be active
+         * @param availabilityPredicate predicate that should return 'true' only when this device
+         *                              state is allowed
+         * @param initializer callback that will be called when the system is booted and ready
+         */
+        private DeviceStatePredicateWrapper(
+                @NonNull DeviceState deviceState,
+                @NonNull Predicate<FoldableDeviceStateProvider> activeStatePredicate,
+                @NonNull Predicate<FoldableDeviceStateProvider> availabilityPredicate,
+                @Nullable Runnable initializer) {
 
             mDeviceState = deviceState;
             mActiveStatePredicate = activeStatePredicate;
             mAvailabilityPredicate = availabilityPredicate;
+            mInitializer = initializer;
         }
 
         /** Create a configuration with an active state predicate **/
@@ -487,6 +516,16 @@
             return new DeviceStatePredicateWrapper(deviceState, activeStatePredicate);
         }
 
+        /** Create a configuration with an active state predicate and an initializer **/
+        public static DeviceStatePredicateWrapper createConfig(
+                @NonNull DeviceState deviceState,
+                @NonNull Predicate<FoldableDeviceStateProvider> activeStatePredicate,
+                @Nullable Runnable initializer
+        ) {
+            return new DeviceStatePredicateWrapper(deviceState, activeStatePredicate, ALLOWED,
+                    initializer);
+        }
+
         /** Create a configuration with availability and active state predicate **/
         public static DeviceStatePredicateWrapper createConfig(
                 @NonNull DeviceState deviceState,
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig
index 21e33dd..da2e5ee 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig
@@ -9,6 +9,16 @@
 }
 
 flag {
+    name: "force_foldables_tent_mode_with_screen_wakelock"
+    namespace: "windowing_frontend"
+    description: "Switching displays on a foldable device later if screen wakelock is present"
+    bug: "363174979"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "enable_foldables_posture_based_closed_state"
     namespace: "windowing_frontend"
     description: "Enables smarter closed device state state for foldable devices"
diff --git a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java
index 2d725d1..c25d5ef 100644
--- a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java
+++ b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java
@@ -45,6 +45,8 @@
 import android.hardware.display.DisplayManager;
 import android.hardware.input.InputSensorInfo;
 import android.os.Handler;
+import android.os.PowerManager;
+import android.os.PowerManager.ScreenTimeoutPolicyListener;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableContext;
 import android.view.Display;
@@ -98,6 +100,8 @@
     @Mock
     DisplayManager mDisplayManager;
     @Mock
+    PowerManager mPowerManager;
+    @Mock
     private Display mDisplay;
 
     private final FakeFeatureFlagsImpl mFakeFeatureFlags = new FakeFeatureFlagsImpl();
@@ -118,6 +122,7 @@
 
     private Map<Sensor, List<SensorEventListener>> mSensorEventListeners = new HashMap<>();
     private DeviceStateProvider mProvider;
+    private BookStyleDeviceStatePolicy mPolicy;
 
     @Before
     public void setup() {
@@ -125,6 +130,7 @@
 
         mFakeFeatureFlags.setFlag(Flags.FLAG_ENABLE_FOLDABLES_POSTURE_BASED_CLOSED_STATE, true);
         mFakeFeatureFlags.setFlag(Flags.FLAG_ENABLE_DUAL_DISPLAY_BLOCKING, true);
+        mFakeFeatureFlags.setFlag(Flags.FLAG_FORCE_FOLDABLES_TENT_MODE_WITH_SCREEN_WAKELOCK, true);
 
         when(mInputSensorInfo.getName()).thenReturn("hall-effect");
         mHallSensor = new Sensor(mInputSensorInfo);
@@ -146,6 +152,7 @@
 
         when(mDisplayManager.getDisplay(eq(DEFAULT_DISPLAY))).thenReturn(mDisplay);
         mContext.addMockSystemService(DisplayManager.class, mDisplayManager);
+        mContext.addMockSystemService(PowerManager.class, mPowerManager);
 
         mContext.ensureTestableResources();
         when(mContext.getResources().getConfiguration()).thenReturn(mConfiguration);
@@ -592,6 +599,62 @@
     }
 
     @Test
+    public void test_unfoldTo85Degrees_screenWakeLockExists_forceTentModeWithWakeLockEnabled()
+            throws Exception {
+        mFakeFeatureFlags.setFlag(Flags.FLAG_FORCE_FOLDABLES_TENT_MODE_WITH_SCREEN_WAKELOCK, true);
+        mInstrumentation.runOnMainSync(() -> mProvider = createProvider());
+        mPolicy.getDeviceStateProvider().onSystemReady();
+        sendHingeAngle(0f);
+        final ScreenTimeoutPolicyListener listener = captureScreenTimeoutPolicyListener();
+        listener.onScreenTimeoutPolicyChanged(PowerManager.SCREEN_TIMEOUT_KEEP_DISPLAY_ON);
+        mProvider.setListener(mListener);
+        assertLatestReportedState(DEVICE_STATE_CLOSED);
+        sendHingeAngle(180f);
+        assertLatestReportedState(DEVICE_STATE_OPENED);
+        sendHingeAngle(0f);
+        sendHingeAngle(15f);
+        assertLatestReportedState(DEVICE_STATE_CLOSED);
+
+        sendHingeAngle(85f);
+
+        // Keeps 'closed' state meaning that it is in 'tent' mode as we have a screen wakelock
+        assertLatestReportedState(DEVICE_STATE_CLOSED);
+    }
+
+    @Test
+    public void test_unfoldTo85Degrees_noScreenWakelock_forceTentModeWithWakeLockEnabled()
+            throws Exception {
+        mFakeFeatureFlags.setFlag(Flags.FLAG_FORCE_FOLDABLES_TENT_MODE_WITH_SCREEN_WAKELOCK, true);
+        mInstrumentation.runOnMainSync(() -> mProvider = createProvider());
+        mPolicy.getDeviceStateProvider().onSystemReady();
+        sendHingeAngle(0f);
+        final ScreenTimeoutPolicyListener listener = captureScreenTimeoutPolicyListener();
+        listener.onScreenTimeoutPolicyChanged(PowerManager.SCREEN_TIMEOUT_ACTIVE);
+        mProvider.setListener(mListener);
+        assertLatestReportedState(DEVICE_STATE_CLOSED);
+        sendHingeAngle(180f);
+        assertLatestReportedState(DEVICE_STATE_OPENED);
+        sendHingeAngle(0f);
+        assertLatestReportedState(DEVICE_STATE_CLOSED);
+
+        sendHingeAngle(85f);
+
+        // Switches to half-opened state as we don't have a screen wakelock
+        assertLatestReportedState(DEVICE_STATE_HALF_OPENED);
+    }
+
+    @Test
+    public void test_unfoldTo85Degrees_notSubscribedToWakeLocks_forceTentModeWithWakeLockDisabled()
+            throws Exception {
+        mFakeFeatureFlags.setFlag(Flags.FLAG_FORCE_FOLDABLES_TENT_MODE_WITH_SCREEN_WAKELOCK, false);
+        mInstrumentation.runOnMainSync(() -> mProvider = createProvider());
+
+        mPolicy.getDeviceStateProvider().onSystemReady();
+
+        verify(mPowerManager, never()).addScreenTimeoutPolicyListener(anyInt(), any(), any());
+    }
+
+    @Test
     public void test_foldTo10_leftSideIsFlat_keepsInnerScreenForReverseWedge() {
         sendHingeAngle(180f);
         sendLeftSideFlatSensorEvent(true);
@@ -751,8 +814,17 @@
     }
 
     private DeviceStateProvider createProvider() {
-        return new BookStyleDeviceStatePolicy(mFakeFeatureFlags, mContext, mHingeAngleSensor,
+        mPolicy = new BookStyleDeviceStatePolicy(mFakeFeatureFlags, mContext, mHingeAngleSensor,
                 mHallSensor, mLeftAccelerometer, mRightAccelerometer,
-                /* closeAngleDegrees= */ null).getDeviceStateProvider();
+                /* closeAngleDegrees= */ null);
+        return mPolicy.getDeviceStateProvider();
+    }
+
+    private ScreenTimeoutPolicyListener captureScreenTimeoutPolicyListener() {
+        final ArgumentCaptor<ScreenTimeoutPolicyListener> captor = ArgumentCaptor
+                .forClass(ScreenTimeoutPolicyListener.class);
+        verify(mPowerManager, atLeastOnce())
+                .addScreenTimeoutPolicyListener(anyInt(), any(), captor.capture());
+        return captor.getValue();
     }
 }
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
index 8ce2422..70eeae6 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
@@ -50,6 +50,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.server.pm.UserManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 
 import org.junit.Before;
@@ -74,6 +75,11 @@
         super.setUp();
         ImeVisibilityStateComputer.Injector injector = new ImeVisibilityStateComputer.Injector() {
             @Override
+            public UserManagerInternal getUserManagerService() {
+                return mMockUserManagerInternal;
+            }
+
+            @Override
             public WindowManagerInternal getWmService() {
                 return mMockWindowManagerInternal;
             }
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
index b984624..6adb01c 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java
@@ -23,6 +23,7 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.server.pm.UserManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 
 import org.junit.After;
@@ -49,6 +50,9 @@
     private InputMethodManagerService mMockInputMethodManagerService;
 
     @Mock
+    private UserManagerInternal mMockUserManagerInternal;
+
+    @Mock
     private WindowManagerInternal mMockWindowManagerInternal;
 
     @NonNull
@@ -70,6 +74,12 @@
                 new ImeVisibilityStateComputer.Injector() {
                     @NonNull
                     @Override
+                    public UserManagerInternal getUserManagerService() {
+                        return mMockUserManagerInternal;
+                    }
+
+                    @NonNull
+                    @Override
                     public WindowManagerInternal getWmService() {
                         return mMockWindowManagerInternal;
                     }
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java
index 58e4b91..8f26bf3 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java
@@ -72,8 +72,6 @@
     private static final String PACKAGE_CHANGED_TEST_PACKAGE_NAME = "testpackagename";
     private static final String PACKAGE_CHANGED_TEST_MAIN_ACTIVITY =
             PACKAGE_CHANGED_TEST_PACKAGE_NAME + ".MainActivity";
-    private static final String PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED =
-            "android.permission.INTERNAL_RECEIVE_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED";
 
     @Rule
     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@@ -125,34 +123,23 @@
 
     @RequiresFlagsEnabled(FLAG_REDUCE_BROADCASTS_FOR_COMPONENT_STATE_CHANGES)
     @Test
-    public void changeNonExportedComponent_sendPackageChangedBroadcastToSystem_withPermission()
+    public void changeNonExportedComponent_sendPackageChangedBroadcastToSystemAndApplicationItself()
             throws Exception {
         changeComponentAndSendPackageChangedBroadcast(false /* changeExportedComponent */,
                 new String[0] /* sharedPackages */);
 
-        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
-        verify(mMockActivityManagerInternal).broadcastIntentWithCallback(
-                captor.capture(), eq(null),
-                eq(new String[]{PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED}),
-                anyInt(), eq(null), eq(null), eq(null));
-        Intent intent = captor.getValue();
-        assertNotNull(intent);
-        assertThat(intent.getPackage()).isEqualTo("android");
-    }
+        ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class);
+        verify(mMockActivityManagerInternal, times(2)).broadcastIntentWithCallback(
+                captorIntent.capture(), eq(null), eq(null), anyInt(), eq(null), eq(null), eq(null));
+        List<Intent> intents = captorIntent.getAllValues();
+        assertNotNull(intents);
+        assertThat(intents.size()).isEqualTo(2);
 
-    @RequiresFlagsEnabled(FLAG_REDUCE_BROADCASTS_FOR_COMPONENT_STATE_CHANGES)
-    @Test
-    public void changeNonExportedComponent_sendPackageChangedBroadcastToApplicationItself()
-            throws Exception {
-        changeComponentAndSendPackageChangedBroadcast(false /* changeExportedComponent */,
-                new String[0] /* sharedPackages */);
+        final Intent intent1 = intents.get(0);
+        assertThat(intent1.getPackage()).isEqualTo("android");
 
-        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
-        verify(mMockActivityManagerInternal).broadcastIntentWithCallback(captor.capture(), eq(null),
-                eq(null), anyInt(), eq(null), eq(null), eq(null));
-        Intent intent = captor.getValue();
-        assertNotNull(intent);
-        assertThat(intent.getPackage()).isEqualTo(PACKAGE_CHANGED_TEST_PACKAGE_NAME);
+        final Intent intent2 = intents.get(1);
+        assertThat(intent2.getPackage()).isEqualTo(PACKAGE_CHANGED_TEST_PACKAGE_NAME);
     }
 
     @RequiresFlagsEnabled(FLAG_REDUCE_BROADCASTS_FOR_COMPONENT_STATE_CHANGES)
@@ -163,31 +150,20 @@
                 new String[]{"shared.package"} /* sharedPackages */);
 
         ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class);
-        ArgumentCaptor<String[]> captorRequiredPermissions = ArgumentCaptor.forClass(
-                String[].class);
         verify(mMockActivityManagerInternal, times(3)).broadcastIntentWithCallback(
-                captorIntent.capture(), eq(null), captorRequiredPermissions.capture(), anyInt(),
-                eq(null), eq(null), eq(null));
+                captorIntent.capture(), eq(null), eq(null), anyInt(), eq(null), eq(null), eq(null));
         List<Intent> intents = captorIntent.getAllValues();
-        List<String[]> requiredPermissions = captorRequiredPermissions.getAllValues();
         assertNotNull(intents);
         assertThat(intents.size()).isEqualTo(3);
 
         final Intent intent1 = intents.get(0);
-        final String[] requiredPermission1 = requiredPermissions.get(0);
         assertThat(intent1.getPackage()).isEqualTo("android");
-        assertThat(requiredPermission1).isEqualTo(
-                new String[]{PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED});
 
         final Intent intent2 = intents.get(1);
-        final String[] requiredPermission2 = requiredPermissions.get(1);
         assertThat(intent2.getPackage()).isEqualTo(PACKAGE_CHANGED_TEST_PACKAGE_NAME);
-        assertThat(requiredPermission2).isNull();
 
         final Intent intent3 = intents.get(2);
-        final String[] requiredPermission3 = requiredPermissions.get(2);
         assertThat(intent3.getPackage()).isEqualTo("shared.package");
-        assertThat(requiredPermission3).isNull();
     }
 
     @Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java
index f442eb6..ebaa2e8 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java
@@ -271,4 +271,31 @@
         assertThat(center[0]).isEqualTo(expected[0]);
         assertThat(center[1]).isEqualTo(expected[1]);
     }
+
+    @Test
+    public void getS2CellApproximateEdge_returnsCorrectRadius() {
+        int level = 10;
+
+        float radius = mFudger.getS2CellApproximateEdge(level);
+
+        assertThat(radius).isEqualTo(9000);  // in meters
+    }
+
+    @Test
+    public void getS2CellApproximateEdge_doesNotThrow() {
+        int level = -1;
+
+        mFudger.getS2CellApproximateEdge(level);
+
+        // No exception thrown.
+    }
+
+    @Test
+    public void getS2CellApproximateEdge_doesNotThrow2() {
+        int level = 14;
+
+        mFudger.getS2CellApproximateEdge(level);
+
+        // No exception thrown.
+    }
 }
diff --git a/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java
index db58c74..29f55ff 100644
--- a/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java
+++ b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java
@@ -20,6 +20,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -81,7 +82,7 @@
     }
 
     @Test
-    public void testWriteAuditLogs() {
+    public void testWriteAuditLogs() throws Exception {
         writeTestLog("granted", "perm", TEST_DOMAIN, "ttype", "tclass");
         writeTestLog("denied", "perm1", TEST_DOMAIN, "ttype1", "tclass1");
 
@@ -117,7 +118,7 @@
     }
 
     @Test
-    public void testWriteAuditLogs_multiplePerms() {
+    public void testWriteAuditLogs_multiplePerms() throws Exception {
         writeTestLog("denied", "perm1 perm2", TEST_DOMAIN, "ttype", "tclass");
         writeTestLog("denied", "perm3 perm4", TEST_DOMAIN, "ttype", "tclass");
 
@@ -153,7 +154,7 @@
     }
 
     @Test
-    public void testWriteAuditLogs_withPaths() {
+    public void testWriteAuditLogs_withPaths() throws Exception {
         writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", "/good/path");
         writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", "/very/long/path");
         writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", "/short_path");
@@ -217,7 +218,7 @@
     }
 
     @Test
-    public void testWriteAuditLogs_withCategories() {
+    public void testWriteAuditLogs_withCategories() throws Exception {
         writeTestLog("denied", "perm", TEST_DOMAIN, new int[] {123}, "ttype", null, "tclass");
         writeTestLog("denied", "perm", TEST_DOMAIN, new int[] {123, 456}, "ttype", null, "tclass");
         writeTestLog("denied", "perm", TEST_DOMAIN, null, "ttype", new int[] {666}, "tclass");
@@ -288,7 +289,7 @@
     }
 
     @Test
-    public void testWriteAuditLogs_withPathAndCategories() {
+    public void testWriteAuditLogs_withPathAndCategories() throws Exception {
         writeTestLog(
                 "denied",
                 "perm",
@@ -318,7 +319,7 @@
     }
 
     @Test
-    public void testWriteAuditLogs_permissive() {
+    public void testWriteAuditLogs_permissive() throws Exception {
         writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
         writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", true);
         writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass", false);
@@ -356,7 +357,7 @@
     }
 
     @Test
-    public void testNotWriteAuditLogs_notTestDomain() {
+    public void testNotWriteAuditLogs_notTestDomain() throws Exception {
         writeTestLog("denied", "perm", "stype", "ttype", "tclass");
 
         boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
@@ -379,7 +380,7 @@
     }
 
     @Test
-    public void testWriteAuditLogs_upToQuota() {
+    public void testWriteAuditLogs_upToQuota() throws Exception {
         writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
         writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
         writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
@@ -389,9 +390,9 @@
         writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
         writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
 
-        boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+        assertThrows(QuotaExceededException.class, () ->
+                mSelinuxAutidLogsCollector.collect(ANSWER_TAG));
 
-        assertThat(done).isTrue();
         verify(
                 () ->
                         FrameworkStatsLog.write(
@@ -409,7 +410,7 @@
     }
 
     @Test
-    public void testWriteAuditLogs_resetQuota() {
+    public void testWriteAuditLogs_resetQuota() throws Exception {
         writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
         writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
         writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
@@ -418,8 +419,8 @@
         writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
         writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
 
-        boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
-        assertThat(done).isTrue();
+        assertThrows(QuotaExceededException.class, () ->
+                mSelinuxAutidLogsCollector.collect(ANSWER_TAG));
         verify(
                 () ->
                         FrameworkStatsLog.write(
@@ -442,8 +443,8 @@
         writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
         // move the clock forward to reset the quota limiter.
         mClock.currentTimeMillis += Duration.ofHours(1).toMillis();
-        done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
-        assertThat(done).isTrue();
+        assertThrows(QuotaExceededException.class, () ->
+                mSelinuxAutidLogsCollector.collect(ANSWER_TAG));
         verify(
                 () ->
                         FrameworkStatsLog.write(
@@ -461,15 +462,12 @@
     }
 
     @Test
-    public void testNotWriteAuditLogs_stopRequested() {
+    public void testNotWriteAuditLogs_stopRequested() throws Exception {
         writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
         writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
         writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
         writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
         writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
-        // These are not pushed.
-        writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
-        writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
 
         mSelinuxAutidLogsCollector.setStopRequested(true);
         boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
@@ -509,7 +507,7 @@
     }
 
     @Test
-    public void testAuditLogs_resumeJobDoesNotExceedLimit() {
+    public void testAuditLogs_resumeJobDoesNotExceedLimit() throws Exception {
         writeTestLog("denied", "perm", TEST_DOMAIN, "ttype", "tclass");
         mSelinuxAutidLogsCollector.setStopRequested(true);
 
diff --git a/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsJobTest.java b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsJobTest.java
index 2aea8a0..344f329 100644
--- a/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsJobTest.java
+++ b/services/tests/selinux/src/com/android/server/selinux/SelinuxAuditLogsJobTest.java
@@ -53,7 +53,7 @@
     }
 
     @Test
-    public void testFinishSuccessfully() {
+    public void testFinishSuccessfully() throws Exception {
         when(mAuditLogsCollector.collect(anyInt())).thenReturn(true);
 
         mAuditLogsJob.start(mJobService, mParams);
@@ -63,7 +63,7 @@
     }
 
     @Test
-    public void testInterrupt() {
+    public void testInterrupt() throws Exception {
         when(mAuditLogsCollector.collect(anyInt())).thenReturn(false);
 
         mAuditLogsJob.start(mJobService, mParams);
@@ -73,7 +73,7 @@
     }
 
     @Test
-    public void testInterruptAndResume() {
+    public void testInterruptAndResume() throws Exception {
         when(mAuditLogsCollector.collect(anyInt())).thenReturn(false);
         mAuditLogsJob.start(mJobService, mParams);
         verify(mJobService, never()).jobFinished(any(), anyBoolean());
@@ -85,7 +85,7 @@
     }
 
     @Test
-    public void testRequestStop() throws InterruptedException {
+    public void testRequestStop() throws Exception {
         Semaphore isRunning = new Semaphore(0);
         Semaphore stopRequested = new Semaphore(0);
         AtomicReference<Throwable> uncaughtException = new AtomicReference<>();
diff --git a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
index 92c6db5..5240f58 100644
--- a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
@@ -166,8 +166,7 @@
                 new GestureLauncherService(
                         mContext, mMetricsLogger, mQuickAccessWalletClient, mUiEventLogger);
 
-        withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
-        withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+        Settings.Secure.clearProviderForTest();
     }
 
     private WalletLaunchedReceiver registerWalletLaunchedReceiver(String action) {
@@ -223,7 +222,7 @@
         withDoubleTapPowerModeConfigValue(
                 DOUBLE_TAP_POWER_DISABLED_MODE);
         withMultiTargetDoubleTapPowerGestureEnableSettingValue(false);
-        withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+        withDoubleTapPowerGestureActionSettingValue(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
 
         assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
                 mContext, FAKE_USER_ID));
@@ -244,7 +243,7 @@
     public void testIsCameraDoubleTapPowerSettingEnabled_flagEnabled_configFalseSettingEnabled() {
         withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_DISABLED_MODE);
         withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
-        withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+        withDoubleTapPowerGestureActionSettingValue(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
 
         assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
                 mContext, FAKE_USER_ID));
@@ -265,7 +264,7 @@
     public void testIsCameraDoubleTapPowerSettingEnabled_flagEnabled_configTrueSettingDisabled() {
         withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
         withMultiTargetDoubleTapPowerGestureEnableSettingValue(false);
-        withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+        withDoubleTapPowerGestureActionSettingValue(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
 
         assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
                 mContext, FAKE_USER_ID));
@@ -286,7 +285,7 @@
     public void testIsCameraDoubleTapPowerSettingEnabled_flagEnabled_configTrueSettingEnabled() {
         withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
         withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
-        withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+        withDoubleTapPowerGestureActionSettingValue(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
 
         assertTrue(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
                 mContext, FAKE_USER_ID));
@@ -329,7 +328,31 @@
     public void testIsCameraDoubleTapPowerSettingEnabled_actionWallet() {
         withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
         withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
-        withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
+        withDoubleTapPowerGestureActionSettingValue(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
+
+        assertFalse(
+                mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
+                        mContext, FAKE_USER_ID));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+    public void testIsCameraDoubleTapPowerSettingEnabled_defaultActionCamera() {
+        withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
+        withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
+        withDefaultDoubleTapPowerGestureActionConfig(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+
+        assertTrue(
+                mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
+                        mContext, FAKE_USER_ID));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+    public void testIsCameraDoubleTapPowerSettingEnabled_defaultActionNotCamera() {
+        withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
+        withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
+        withDefaultDoubleTapPowerGestureActionConfig(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
 
         assertFalse(
                 mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
@@ -341,7 +364,7 @@
     public void testIsWalletDoubleTapPowerSettingEnabled() {
         withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
         withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
-        withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
+        withDoubleTapPowerGestureActionSettingValue(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
 
         assertTrue(
                 mGestureLauncherService.isWalletDoubleTapPowerSettingEnabled(
@@ -353,7 +376,7 @@
     public void testIsWalletDoubleTapPowerSettingEnabled_configDisabled() {
         withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_DISABLED_MODE);
         withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
-        withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
+        withDoubleTapPowerGestureActionSettingValue(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
 
         assertFalse(
                 mGestureLauncherService.isWalletDoubleTapPowerSettingEnabled(
@@ -365,7 +388,7 @@
     public void testIsWalletDoubleTapPowerSettingEnabled_settingDisabled() {
         withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
         withMultiTargetDoubleTapPowerGestureEnableSettingValue(false);
-        withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
+        withDoubleTapPowerGestureActionSettingValue(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
 
         assertFalse(
                 mGestureLauncherService.isWalletDoubleTapPowerSettingEnabled(
@@ -377,7 +400,31 @@
     public void testIsWalletDoubleTapPowerSettingEnabled_actionCamera() {
         withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
         withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
-        withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+        withDoubleTapPowerGestureActionSettingValue(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+
+        assertFalse(
+                mGestureLauncherService.isWalletDoubleTapPowerSettingEnabled(
+                        mContext, FAKE_USER_ID));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+    public void testIsWalletDoubleTapPowerSettingEnabled_defaultActionWallet() {
+        withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
+        withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
+        withDefaultDoubleTapPowerGestureActionConfig(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
+
+        assertTrue(
+                mGestureLauncherService.isWalletDoubleTapPowerSettingEnabled(
+                        mContext, FAKE_USER_ID));
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+    public void testIsWalletDoubleTapPowerSettingEnabled_defaultActionNotWallet() {
+        withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
+        withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
+        withDefaultDoubleTapPowerGestureActionConfig(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
 
         assertFalse(
                 mGestureLauncherService.isWalletDoubleTapPowerSettingEnabled(
@@ -1858,7 +1905,7 @@
                 UserHandle.USER_CURRENT);
     }
 
-    private void withDefaultDoubleTapPowerGestureAction(int action) {
+    private void withDoubleTapPowerGestureActionSettingValue(int action) {
         Settings.Secure.putIntForUser(
                 mContentResolver,
                 Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE,
@@ -1866,6 +1913,12 @@
                 UserHandle.USER_CURRENT);
     }
 
+    private void withDefaultDoubleTapPowerGestureActionConfig(int action) {
+        when(mResources.getInteger(
+                com.android.internal.R.integer.config_doubleTapPowerGestureMultiTargetDefaultAction
+        )).thenReturn(action);
+    }
+
     private void withEmergencyGestureEnabledConfigValue(boolean enableConfigValue) {
         when(mResources.getBoolean(
                 com.android.internal.R.bool.config_emergencyGestureEnabled))
@@ -1931,7 +1984,7 @@
     }
 
     private void enableWalletGesture() {
-        withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
+        withDoubleTapPowerGestureActionSettingValue(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
         withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
         withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
 
@@ -1951,7 +2004,7 @@
             withDoubleTapPowerModeConfigValue(
                     DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
             withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
-            withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+            withDoubleTapPowerGestureActionSettingValue(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
         } else {
             withCameraDoubleTapPowerEnableConfigValue(true);
             withCameraDoubleTapPowerDisableSettingValue(0);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index dafe482..5602fb7 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -829,26 +829,6 @@
 
     @SmallTest
     @Test
-    @DisableFlags(com.android.systemui.Flags.FLAG_HEARING_AIDS_QS_TILE_DIALOG)
-    public void testPerformAccessibilityShortcut_hearingAids_startActivityWithExpectedComponent() {
-        final AccessibilityUserState userState = mA11yms.mUserStates.get(
-                mA11yms.getCurrentUserIdLocked());
-        mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
-        userState.updateShortcutTargetsLocked(
-                Set.of(ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString()), HARDWARE);
-
-        mA11yms.performAccessibilityShortcut(
-                Display.DEFAULT_DISPLAY, HARDWARE,
-                ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString());
-        mTestableLooper.processAllMessages();
-
-        assertStartActivityWithExpectedComponentName(mTestableContext.getMockContext(),
-                ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString());
-    }
-
-    @SmallTest
-    @Test
-    @EnableFlags(com.android.systemui.Flags.FLAG_HEARING_AIDS_QS_TILE_DIALOG)
     public void testPerformAccessibilityShortcut_hearingAids_sendExpectedBroadcast() {
         final AccessibilityUserState userState = mA11yms.mUserStates.get(
                 mA11yms.getCurrentUserIdLocked());
@@ -1510,7 +1490,6 @@
     }
 
     @Test
-    @EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
     public void notifyQuickSettingsTilesChanged_statusBarServiceNotGranted_throwsException() {
         mFakePermissionEnforcer.revoke(Manifest.permission.STATUS_BAR_SERVICE);
         mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
@@ -1523,7 +1502,6 @@
     }
 
     @Test
-    @EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
     public void notifyQuickSettingsTilesChanged_manageAccessibilityNotGranted_throwsException() {
         mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
         mTestableContext.getTestablePermissions().setPermission(
@@ -1537,7 +1515,6 @@
     }
 
     @Test
-    @EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
     public void notifyQuickSettingsTilesChanged_qsTileChanges_updateA11yTilesInQsPanel() {
         mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
         mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
@@ -1557,7 +1534,6 @@
     }
 
     @Test
-    @EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
     public void notifyQuickSettingsTilesChanged_sameQsTiles_noUpdateToA11yTilesInQsPanel() {
         notifyQuickSettingsTilesChanged_qsTileChanges_updateA11yTilesInQsPanel();
         List<ComponentName> tiles =
@@ -1574,7 +1550,6 @@
     }
 
     @Test
-    @EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
     public void notifyQuickSettingsTilesChanged_serviceWarningRequired_qsShortcutRemainDisabled() {
         mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
         mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
@@ -1593,7 +1568,6 @@
     }
 
     @Test
-    @EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
     public void notifyQuickSettingsTilesChanged_serviceWarningNotRequired_qsShortcutEnabled() {
         mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
         mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
@@ -1615,7 +1589,6 @@
     }
 
     @Test
-    @EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
     public void notifyQuickSettingsTilesChanged_addFrameworkTile_qsShortcutEnabled() {
         mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
         mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY);
@@ -1638,7 +1611,6 @@
     }
 
     @Test
-    @EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
     public void notifyQuickSettingsTilesChanged_removeFrameworkTile_qsShortcutDisabled() {
         notifyQuickSettingsTilesChanged_addFrameworkTile_qsShortcutEnabled();
         Set<ComponentName> qsTiles = mA11yms.getCurrentUserState().getA11yQsTilesInQsPanel();
@@ -1656,7 +1628,6 @@
     }
 
     @Test
-    @EnableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
     @DisableFlags(android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SECURE_SETTINGS_ON_HSUM_DEVICE)
     public void restoreShortcutTargetsAssumeUser0_qs_a11yQsTargetsRestored() {
         assumeTrue("The test is setup to run as a user 0",
@@ -1665,8 +1636,7 @@
     }
 
     @Test
-    @EnableFlags({android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT,
-            android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SECURE_SETTINGS_ON_HSUM_DEVICE})
+    @EnableFlags(android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SECURE_SETTINGS_ON_HSUM_DEVICE)
     public void restoreShortcutTargets_qs_a11yQsTargetsRestored() {
         String daltonizerTile =
                 AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString();
@@ -1689,40 +1659,6 @@
     }
 
     @Test
-    @DisableFlags({android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT,
-            android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SECURE_SETTINGS_ON_HSUM_DEVICE})
-    public void restoreShortcutTargetsAssumeUser0_qs_a11yQsTargetsNotRestored() {
-        assumeTrue("The test is setup to run as a user 0",
-                mTestableContext.getUserId() == UserHandle.USER_SYSTEM);
-        restoreShortcutTargets_qs_a11yQsTargetsNotRestored();
-    }
-
-    @Test
-    @DisableFlags(android.view.accessibility.Flags.FLAG_A11Y_QS_SHORTCUT)
-    @EnableFlags(android.view.accessibility.Flags.FLAG_RESTORE_A11Y_SECURE_SETTINGS_ON_HSUM_DEVICE)
-    public void restoreShortcutTargets_qs_a11yQsTargetsNotRestored() {
-        String daltonizerTile =
-                AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME.flattenToString();
-        String colorInversionTile =
-                AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME.flattenToString();
-        final AccessibilityUserState userState = new AccessibilityUserState(
-                mA11yms.getCurrentUserIdLocked(), mTestableContext, mA11yms);
-        userState.updateShortcutTargetsLocked(Set.of(daltonizerTile), QUICK_SETTINGS);
-        putShortcutSettingForUser(QUICK_SETTINGS, daltonizerTile, userState.mUserId);
-        mA11yms.mUserStates.put(userState.mUserId, userState);
-
-        broadcastSettingRestored(
-                ShortcutUtils.convertToKey(QUICK_SETTINGS),
-                /*newValue=*/colorInversionTile, userState.mUserId);
-
-        Set<String> expected = Set.of(daltonizerTile);
-        assertThat(readStringsFromSetting(ShortcutUtils.convertToKey(QUICK_SETTINGS)))
-                .containsExactlyElementsIn(expected);
-        assertThat(userState.getShortcutTargetsLocked(QUICK_SETTINGS))
-                .containsExactlyElementsIn(expected);
-    }
-
-    @Test
     public void onHandleForceStop_dontDoIt_packageEnabled_returnsTrue() {
         setupShortcutTargetServices();
         AccessibilityUserState userState = mA11yms.getCurrentUserState();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 861e72d..cfdf176 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -2868,6 +2868,9 @@
 
         assertThat(mPowerManager.isInteractive()).isTrue();
         mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+
         mTestLooper.moveTimeForward(MONITORING_INTERVAL_MS);
         mTestLooper.dispatchAll();
 
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 776f05d..f536cae 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -167,7 +167,7 @@
      * Test for {@link ShortcutService#getLastResetTimeLocked()} and
      * {@link ShortcutService#getNextResetTimeLocked()}.
      */
-    public void testUpdateAndGetNextResetTimeLocked() {
+    public void disabled_testUpdateAndGetNextResetTimeLocked() {
         assertResetTimes(START_TIME, START_TIME + INTERVAL);
 
         // Advance clock.
@@ -284,7 +284,7 @@
         assertEquals(START_TIME + 5 * INTERVAL, mManager.getRateLimitResetTime());
     }
 
-    public void testSetDynamicShortcuts() {
+    public void disabled_testSetDynamicShortcuts() {
         setCaller(CALLING_PACKAGE_1, USER_10);
 
         final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.icon1);
@@ -596,7 +596,7 @@
                 eq(CALLING_PACKAGE_2), any(), eq(USER_10));
     }
 
-    public void testUnlimitedCalls() {
+    public void disabled_testUnlimitedCalls() {
         setCaller(CALLING_PACKAGE_1, USER_10);
 
         final ShortcutInfo si1 = makeShortcut("shortcut1");
@@ -1205,7 +1205,7 @@
                         maxSize));
     }
 
-    public void testShrinkBitmap() {
+    public void disabled_testShrinkBitmap() {
         checkShrinkBitmap(32, 32, R.drawable.black_512x512, 32);
         checkShrinkBitmap(511, 511, R.drawable.black_512x512, 511);
         checkShrinkBitmap(512, 512, R.drawable.black_512x512, 512);
@@ -1302,7 +1302,7 @@
         assertFalse(p11_1_3.getName().contains("_"));
     }
 
-    public void testUpdateShortcuts() {
+    public void disabled_testUpdateShortcuts() {
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
             assertTrue(mManager.setDynamicShortcuts(list(
                     makeShortcut("s1"),
@@ -1433,7 +1433,7 @@
         });
     }
 
-    public void testUpdateShortcuts_icons() {
+    public void disabled_testUpdateShortcuts_icons() {
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
             assertTrue(mManager.setDynamicShortcuts(list(
                     makeShortcut("s1")
@@ -1527,7 +1527,7 @@
         });
     }
 
-    public void testShortcutManagerGetShortcuts_shortcutTypes() {
+    public void disabled_testShortcutManagerGetShortcuts_shortcutTypes() {
 
         // Create 3 manifest and 3 dynamic shortcuts
         addManifestShortcutResource(
@@ -2281,7 +2281,7 @@
         assertEquals("ABC", findById(list, "s1").getTitle());
     }
 
-    public void testPinShortcutAndGetPinnedShortcuts() {
+    public void disabled_testPinShortcutAndGetPinnedShortcuts() {
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
             final ShortcutInfo s1_1 = makeShortcutWithTimestamp("s1", 1000);
             final ShortcutInfo s1_2 = makeShortcutWithTimestamp("s2", 2000);
@@ -3478,7 +3478,7 @@
         });
     }
 
-    public void testStartShortcut() {
+    public void disabled_testStartShortcut() {
         // Create some shortcuts.
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
             final ShortcutInfo s1_1 = makeShortcut(
@@ -3902,7 +3902,7 @@
 
     // === Test for persisting ===
 
-    public void testSaveAndLoadUser_empty() {
+    public void disabled_testSaveAndLoadUser_empty() {
         assertTrue(mManager.setDynamicShortcuts(list()));
 
         Log.i(TAG, "Saved state");
@@ -4074,7 +4074,7 @@
         assertNull(ShortcutPackage.loadFromFile(mService, user, corruptedShortcutPackage, false));
     }
 
-    public void testSaveCorruptAndLoadUser() throws Exception {
+    public void disabled_testSaveCorruptAndLoadUser() throws Exception {
         // First, create some shortcuts and save.
         runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
             final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.black_64x16);
@@ -6658,7 +6658,7 @@
 
     }
 
-    public void testSaveAndLoad_crossProfile() {
+    public void disabled_testSaveAndLoad_crossProfile() {
         prepareCrossProfileDataSet();
 
         dumpsysOnLogcat("Before save & load");
@@ -8471,7 +8471,7 @@
         });
     }
 
-    public void testShortcutsPushedOutByManifest() {
+    public void disabled_testShortcutsPushedOutByManifest() {
         // Change the max number of shortcuts.
         mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=3");
 
@@ -8708,7 +8708,7 @@
         }
     }
 
-    public void testShareTargetInfo_saveToXml() throws IOException, XmlPullParserException {
+    public void disabled_testShareTargetInfo_saveToXml() throws IOException, XmlPullParserException {
         List<ShareTargetInfo> expectedValues = new ArrayList<>();
         expectedValues.add(new ShareTargetInfo(
                 new ShareTargetInfo.TargetData[]{new ShareTargetInfo.TargetData(
@@ -8902,7 +8902,7 @@
         });
     }
 
-    public void testUpdateShortcuts_ExcludesHiddenFromLauncherShortcuts() {
+    public void disabled_testUpdateShortcuts_ExcludesHiddenFromLauncherShortcuts() {
         final ShortcutInfo s1 = makeShortcut("s1");
         final ShortcutInfo s2 = makeShortcut("s2");
         final ShortcutInfo s3 = makeShortcut("s3");
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
index 9528467..39206dc 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
@@ -952,7 +952,7 @@
         assertEquals(99, si.getExtras().getInt("x"));
     }
 
-    public void testShortcutInfoSaveAndLoad() throws InterruptedException {
+    public void disabled_testShortcutInfoSaveAndLoad() throws InterruptedException {
         mRunningUsers.put(USER_11, true);
 
         setCaller(CALLING_PACKAGE_1, USER_11);
@@ -1065,7 +1065,7 @@
         dumpUserFile(USER_11);
     }
 
-    public void testShortcutInfoSaveAndLoad_maskableBitmap() throws InterruptedException {
+    public void disabled_testShortcutInfoSaveAndLoad_maskableBitmap() throws InterruptedException {
         mRunningUsers.put(USER_11, true);
 
         setCaller(CALLING_PACKAGE_1, USER_11);
@@ -1134,7 +1134,7 @@
         dumpUserFile(USER_11);
     }
 
-    public void testShortcutInfoSaveAndLoad_resId() throws InterruptedException {
+    public void disabled_testShortcutInfoSaveAndLoad_resId() throws InterruptedException {
         mRunningUsers.put(USER_11, true);
 
         setCaller(CALLING_PACKAGE_1, USER_11);
@@ -1211,7 +1211,7 @@
         assertEquals(1, si.getRank());
     }
 
-    public void testShortcutInfoSaveAndLoad_uri() throws InterruptedException {
+    public void disabled_testShortcutInfoSaveAndLoad_uri() throws InterruptedException {
         mRunningUsers.put(USER_11, true);
 
         setCaller(CALLING_PACKAGE_1, USER_11);
@@ -1299,7 +1299,7 @@
         assertEquals("uri_maskable", si.getIconUri());
     }
 
-    public void testShortcutInfoSaveAndLoad_forBackup() {
+    public void disabled_testShortcutInfoSaveAndLoad_forBackup() {
         setCaller(CALLING_PACKAGE_1, USER_10);
 
         final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource(
@@ -1368,7 +1368,7 @@
         assertEquals(0, si.getRank());
     }
 
-    public void testShortcutInfoSaveAndLoad_forBackup_resId() {
+    public void disabled_testShortcutInfoSaveAndLoad_forBackup_resId() {
         setCaller(CALLING_PACKAGE_1, USER_10);
 
         final Icon res32x32 = Icon.createWithResource(mClientContext, R.drawable.black_32x32);
@@ -1438,7 +1438,7 @@
         assertEquals(0, si.getRank());
     }
 
-    public void testShortcutInfoSaveAndLoad_forBackup_uri() {
+    public void disabled_testShortcutInfoSaveAndLoad_forBackup_uri() {
         setCaller(CALLING_PACKAGE_1, USER_10);
 
         final Icon uriIcon = Icon.createWithContentUri("test_uri");
@@ -1547,7 +1547,7 @@
                 });
     }
 
-    public void testShortcutInfoSaveAndLoad_intents() {
+    public void disabled_testShortcutInfoSaveAndLoad_intents() {
         checkShortcutInfoSaveAndLoad_intents(new Intent(Intent.ACTION_VIEW));
 
         mInjectedCurrentTimeMillis += INTERVAL; // reset throttling.
@@ -1789,7 +1789,7 @@
         assertFalse(mManager.setDynamicShortcuts(list(si2)));
     }
 
-    public void testThrottling_localeChanges() {
+    public void disabled_testThrottling_localeChanges() {
         prepareCrossProfileDataSet();
 
         dumpsysOnLogcat("Before save & load");
@@ -2078,7 +2078,7 @@
     }
 
 
-    public void testThrottling_resetByInternalCall() throws Exception {
+    public void disabled_testThrottling_resetByInternalCall() throws Exception {
         prepareCrossProfileDataSet();
 
         dumpsysOnLogcat("Before save & load");
@@ -2173,7 +2173,7 @@
         });
     }
 
-    public void testReportShortcutUsed() {
+    public void disabled_testReportShortcutUsed() {
         mRunningUsers.put(USER_11, true);
 
         runWithCaller(CALLING_PACKAGE_1, USER_11, () -> {
@@ -2322,7 +2322,7 @@
                         getTestContext().getPackageName()));
     }
 
-    public void testDumpCheckin() throws IOException {
+    public void disabled_testDumpCheckin() throws IOException {
         prepareCrossProfileDataSet();
 
         // prepareCrossProfileDataSet() doesn't set any icons, so do set here.
diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
index 5518082..7f2935e 100644
--- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
@@ -18,6 +18,7 @@
 
 
 import static android.content.Context.SENSOR_SERVICE;
+import static android.hardware.devicestate.feature.flags.Flags.FLAG_DEVICE_STATE_CONFIGURATION_FLAG;
 
 import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED;
 import static com.android.server.devicestate.DeviceStateProvider.SUPPORTED_DEVICE_STATES_CHANGED_POWER_SAVE_DISABLED;
@@ -42,6 +43,9 @@
 import android.hardware.SensorManager;
 import android.hardware.devicestate.DeviceState;
 import android.os.PowerManager;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 
 import androidx.annotation.NonNull;
 
@@ -51,6 +55,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
@@ -84,6 +89,10 @@
     private Context mContext;
     private SensorManager mSensorManager;
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @Before
     public void setup() {
         LocalServices.addService(InputManagerInternal.class, mock(InputManagerInternal.class));
@@ -605,6 +614,37 @@
         verify(listener, never()).onStateChanged(mIntegerCaptor.capture());
     }
 
+    @RequiresFlagsEnabled(FLAG_DEVICE_STATE_CONFIGURATION_FLAG)
+    @Test
+    public void test_createConfigWithFlags() {
+        String configString = "<device-state-config>\n"
+                + "    <device-state>\n"
+                + "        <identifier>1</identifier>\n"
+                + "        <flags>\n"
+                + "            <flag>FLAG_CANCEL_OVERRIDE_REQUESTS</flag>\n"
+                + "        </flags>\n"
+                + "    </device-state>\n"
+                + "    <device-state>\n"
+                + "        <identifier>2</identifier>\n"
+                + "        <name>CLOSED</name>\n"
+                + "    </device-state>\n"
+                + "</device-state-config>\n";
+        DeviceStateProviderImpl.ReadableConfig config = new TestReadableConfig(configString);
+        DeviceStateProviderImpl provider = DeviceStateProviderImpl.createFromConfig(mContext,
+                config);
+
+        DeviceStateProvider.Listener listener = mock(DeviceStateProvider.Listener.class);
+        provider.setListener(listener);
+
+        verify(listener).onSupportedDeviceStatesChanged(mDeviceStateArrayCaptor.capture(),
+                eq(SUPPORTED_DEVICE_STATES_CHANGED_INITIALIZED));
+        final DeviceState[] expectedStates = new DeviceState[]{
+                createDeviceState(1, "",
+                        Set.of(DeviceState.PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS)),
+                createDeviceState(2, "CLOSED", EMPTY_PROPERTY_SET)};
+        assertArrayEquals(expectedStates, mDeviceStateArrayCaptor.getValue());
+    }
+
     private DeviceState createDeviceState(int identifier, @NonNull String name,
             @NonNull Set<@DeviceState.DeviceStateProperties Integer> systemProperties) {
         DeviceState.Configuration configuration = new DeviceState.Configuration.Builder(identifier,
diff --git a/services/tests/timetests/src/com/android/server/timezonedetector/FakeEnvironment.java b/services/tests/timetests/src/com/android/server/timezonedetector/FakeEnvironment.java
new file mode 100644
index 0000000..5d181b8
--- /dev/null
+++ b/services/tests/timetests/src/com/android/server/timezonedetector/FakeEnvironment.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 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.server.timezonedetector;
+
+import static com.android.server.SystemTimeZone.TIME_ZONE_CONFIDENCE_LOW;
+
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.ElapsedRealtimeLong;
+
+import com.android.server.SystemTimeZone;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A partially implemented, fake implementation of Environment for tests.
+ */
+public class FakeEnvironment implements Environment {
+
+    private final TestState<String> mTimeZoneId = new TestState<>();
+    private final TestState<Integer> mTimeZoneConfidence = new TestState<>();
+    private final List<Runnable> mAsyncRunnables = new ArrayList<>();
+    private @ElapsedRealtimeLong long mElapsedRealtimeMillis;
+    private @CurrentTimeMillisLong long mInitializationTimeMillis;
+
+    FakeEnvironment() {
+        // Ensure the fake environment starts with the defaults a fresh device would.
+        initializeTimeZoneSetting("", TIME_ZONE_CONFIDENCE_LOW);
+    }
+
+    void initializeClock(@CurrentTimeMillisLong long currentTimeMillis,
+            @ElapsedRealtimeLong long elapsedRealtimeMillis) {
+        mInitializationTimeMillis = currentTimeMillis - elapsedRealtimeMillis;
+        mElapsedRealtimeMillis = elapsedRealtimeMillis;
+    }
+
+    void initializeTimeZoneSetting(String zoneId,
+            @SystemTimeZone.TimeZoneConfidence int timeZoneConfidence) {
+        mTimeZoneId.init(zoneId);
+        mTimeZoneConfidence.init(timeZoneConfidence);
+    }
+
+    void incrementClock() {
+        mElapsedRealtimeMillis++;
+    }
+
+    @Override
+    public String getDeviceTimeZone() {
+        return mTimeZoneId.getLatest();
+    }
+
+    @Override
+    public int getDeviceTimeZoneConfidence() {
+        return mTimeZoneConfidence.getLatest();
+    }
+
+    @Override
+    public void setDeviceTimeZoneAndConfidence(
+            String zoneId, @SystemTimeZone.TimeZoneConfidence int confidence, String logInfo) {
+        mTimeZoneId.set(zoneId);
+        mTimeZoneConfidence.set(confidence);
+    }
+
+    void assertTimeZoneNotChanged() {
+        mTimeZoneId.assertHasNotBeenSet();
+        mTimeZoneConfidence.assertHasNotBeenSet();
+    }
+
+    void assertTimeZoneChangedTo(String timeZoneId,
+            @SystemTimeZone.TimeZoneConfidence int confidence) {
+        mTimeZoneId.assertHasBeenSet();
+        mTimeZoneId.assertChangeCount(1);
+        mTimeZoneId.assertLatestEquals(timeZoneId);
+
+        mTimeZoneConfidence.assertHasBeenSet();
+        mTimeZoneConfidence.assertChangeCount(1);
+        mTimeZoneConfidence.assertLatestEquals(confidence);
+    }
+
+    void commitAllChanges() {
+        mTimeZoneId.commitLatest();
+        mTimeZoneConfidence.commitLatest();
+    }
+
+    @Override
+    @ElapsedRealtimeLong
+    public long elapsedRealtimeMillis() {
+        return mElapsedRealtimeMillis;
+    }
+
+    @Override
+    @CurrentTimeMillisLong
+    public long currentTimeMillis() {
+        return mInitializationTimeMillis + mElapsedRealtimeMillis;
+    }
+
+    @Override
+    public void addDebugLogEntry(String logMsg) {
+        // No-op for tests
+    }
+
+    @Override
+    public void dumpDebugLog(PrintWriter printWriter) {
+        // No-op for tests
+    }
+
+    /**
+     * Adds the supplied runnable to a list but does not run them. To run all the runnables that
+     * have been supplied, call {@code #runAsyncRunnables}.
+     */
+    @Override
+    public void runAsync(Runnable runnable) {
+        mAsyncRunnables.add(runnable);
+    }
+
+    /**
+     * Requests that the runnable that have been supplied to {@code #runAsync} are invoked
+     * asynchronously and cleared.
+     */
+    public void runAsyncRunnables() {
+        for (Runnable runnable : mAsyncRunnables) {
+            runnable.run();
+        }
+        mAsyncRunnables.clear();
+    }
+}
diff --git a/services/tests/timetests/src/com/android/server/timezonedetector/NotifyingTimeZoneChangeListenerTest.java b/services/tests/timetests/src/com/android/server/timezonedetector/NotifyingTimeZoneChangeListenerTest.java
index 23c13bd..45cc891 100644
--- a/services/tests/timetests/src/com/android/server/timezonedetector/NotifyingTimeZoneChangeListenerTest.java
+++ b/services/tests/timetests/src/com/android/server/timezonedetector/NotifyingTimeZoneChangeListenerTest.java
@@ -85,9 +85,6 @@
         return List.of(ORIGIN_LOCATION, ORIGIN_TELEPHONY);
     }
 
-    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";
     private static final String INTERACT_ACROSS_USERS_FULL_PERMISSION =
             "android.permission.INTERACT_ACROSS_USERS_FULL";
 
@@ -99,6 +96,7 @@
     private HandlerThread mHandlerThread;
     private TestHandler mHandler;
     private FakeServiceConfigAccessor mServiceConfigAccessor;
+    private FakeEnvironment mFakeEnvironment;
     private int mUid;
 
     private NotifyingTimeZoneChangeListener mTimeZoneChangeTracker;
@@ -106,6 +104,9 @@
     @Before
     public void setUp() {
         mUid = Process.myUid();
+        mFakeEnvironment = new FakeEnvironment();
+        mFakeEnvironment.initializeClock(1735689600L, 1234L);
+
         // Create a thread + handler for processing the work that the service posts.
         mHandlerThread = new HandlerThread("TimeZoneDetectorInternalTest");
         mHandlerThread.start();
@@ -138,7 +139,7 @@
         mNotificationManager = new FakeNotificationManager(mContext, InstantSource.system());
 
         mTimeZoneChangeTracker = new NotifyingTimeZoneChangeListener(mHandler, mContext,
-                mServiceConfigAccessor, mNotificationManager);
+                mServiceConfigAccessor, mNotificationManager, mFakeEnvironment);
     }
 
     @After
@@ -151,7 +152,7 @@
     public void process_autoDetectionOff_noManualTracking_shouldTrackWithoutNotifying() {
         enableTimeZoneNotifications();
 
-        TimeZoneChangeRecord expectedChangeEvent = new TimeZoneChangeRecord(
+        TimeZoneChangeRecord expectedTimeZoneChangeRecord = new TimeZoneChangeRecord(
                 /* id= */ 1,
                 new TimeZoneChangeEvent(
                         /* elapsedRealtimeMillis= */ 0,
@@ -162,13 +163,14 @@
                         /* newZoneId= */ "Europe/London",
                         /* newConfidence= */ 1,
                         /* cause= */ "NO_REASON"));
-        expectedChangeEvent.setStatus(STATUS_UNTRACKED, SIGNAL_TYPE_NONE);
+        expectedTimeZoneChangeRecord.setStatus(STATUS_UNTRACKED, SIGNAL_TYPE_NONE);
 
         assertNull(mTimeZoneChangeTracker.getLastTimeZoneChangeRecord());
 
-        mTimeZoneChangeTracker.process(expectedChangeEvent.getEvent());
+        mTimeZoneChangeTracker.process(expectedTimeZoneChangeRecord.getEvent());
 
-        assertEquals(expectedChangeEvent, mTimeZoneChangeTracker.getLastTimeZoneChangeRecord());
+        assertEquals(expectedTimeZoneChangeRecord,
+                mTimeZoneChangeTracker.getLastTimeZoneChangeRecord());
         assertEquals(0, mNotificationManager.getNotifications().size());
         mHandler.assertTotalMessagesEnqueued(0);
     }
@@ -177,7 +179,7 @@
     public void process_autoDetectionOff_shouldTrackWithoutNotifying() {
         enableNotificationsWithManualChangeTracking();
 
-        TimeZoneChangeRecord expectedChangeEvent = new TimeZoneChangeRecord(
+        TimeZoneChangeRecord expectedTimeZoneChangeRecord = new TimeZoneChangeRecord(
                 /* id= */ 1,
                 new TimeZoneChangeEvent(
                         /* elapsedRealtimeMillis= */ 0,
@@ -188,13 +190,14 @@
                         /* newZoneId= */ "Europe/London",
                         /* newConfidence= */ 1,
                         /* cause= */ "NO_REASON"));
-        expectedChangeEvent.setStatus(STATUS_UNTRACKED, SIGNAL_TYPE_NONE);
+        expectedTimeZoneChangeRecord.setStatus(STATUS_UNTRACKED, SIGNAL_TYPE_NONE);
 
         assertNull(mTimeZoneChangeTracker.getLastTimeZoneChangeRecord());
 
-        mTimeZoneChangeTracker.process(expectedChangeEvent.getEvent());
+        mTimeZoneChangeTracker.process(expectedTimeZoneChangeRecord.getEvent());
 
-        assertEquals(expectedChangeEvent, mTimeZoneChangeTracker.getLastTimeZoneChangeRecord());
+        assertEquals(expectedTimeZoneChangeRecord,
+                mTimeZoneChangeTracker.getLastTimeZoneChangeRecord());
         assertEquals(0, mNotificationManager.getNotifications().size());
         mHandler.assertTotalMessagesEnqueued(1);
     }
@@ -214,7 +217,7 @@
 
         enableNotificationsWithManualChangeTracking();
 
-        TimeZoneChangeRecord expectedChangeEvent = new TimeZoneChangeRecord(
+        TimeZoneChangeRecord expectedTimeZoneChangeRecord = new TimeZoneChangeRecord(
                 /* id= */ 1,
                 new TimeZoneChangeEvent(
                         /* elapsedRealtimeMillis= */ 0,
@@ -225,19 +228,20 @@
                         /* newZoneId= */ "Europe/London",
                         /* newConfidence= */ 1,
                         /* cause= */ "NO_REASON"));
-        expectedChangeEvent.setStatus(STATUS_UNKNOWN, SIGNAL_TYPE_UNKNOWN);
+        expectedTimeZoneChangeRecord.setStatus(STATUS_UNKNOWN, SIGNAL_TYPE_UNKNOWN);
 
         assertNull(mTimeZoneChangeTracker.getLastTimeZoneChangeRecord());
 
         // lastTrackedChangeEvent == null
-        mTimeZoneChangeTracker.process(expectedChangeEvent.getEvent());
-        TimeZoneChangeRecord trackedEvent1 = mTimeZoneChangeTracker.getLastTimeZoneChangeRecord();
+        mTimeZoneChangeTracker.process(expectedTimeZoneChangeRecord.getEvent());
+        TimeZoneChangeRecord timeZoneChangeRecord1 =
+                mTimeZoneChangeTracker.getLastTimeZoneChangeRecord();
 
-        assertEquals(expectedChangeEvent, trackedEvent1);
+        assertEquals(expectedTimeZoneChangeRecord, timeZoneChangeRecord1);
         assertEquals(1, mNotificationManager.getNotifications().size());
         mHandler.assertTotalMessagesEnqueued(1);
 
-        expectedChangeEvent = new TimeZoneChangeRecord(
+        expectedTimeZoneChangeRecord = new TimeZoneChangeRecord(
                 /* id= */ 2,
                 new TimeZoneChangeEvent(
                         /* elapsedRealtimeMillis= */ 1000L,
@@ -248,14 +252,15 @@
                         /* newZoneId= */ "Europe/Paris",
                         /* newConfidence= */ 1,
                         /* cause= */ "NO_REASON"));
-        expectedChangeEvent.setStatus(STATUS_UNKNOWN, SIGNAL_TYPE_UNKNOWN);
+        expectedTimeZoneChangeRecord.setStatus(STATUS_UNKNOWN, SIGNAL_TYPE_UNKNOWN);
 
         // lastTrackedChangeEvent != null
-        mTimeZoneChangeTracker.process(expectedChangeEvent.getEvent());
-        TimeZoneChangeRecord trackedEvent2 = mTimeZoneChangeTracker.getLastTimeZoneChangeRecord();
+        mTimeZoneChangeTracker.process(expectedTimeZoneChangeRecord.getEvent());
+        TimeZoneChangeRecord timeZoneChangeRecord2 =
+                mTimeZoneChangeTracker.getLastTimeZoneChangeRecord();
 
-        assertEquals(STATUS_SUPERSEDED, trackedEvent1.getStatus());
-        assertEquals(expectedChangeEvent, trackedEvent2);
+        assertEquals(STATUS_SUPERSEDED, timeZoneChangeRecord1.getStatus());
+        assertEquals(expectedTimeZoneChangeRecord, timeZoneChangeRecord2);
         assertEquals(2, mNotificationManager.getNotifications().size());
         mHandler.assertTotalMessagesEnqueued(2);
 
@@ -263,7 +268,7 @@
 
         // Test manual change within revert threshold
         {
-            expectedChangeEvent = new TimeZoneChangeRecord(
+            expectedTimeZoneChangeRecord = new TimeZoneChangeRecord(
                     /* id= */ 3,
                     new TimeZoneChangeEvent(
                             /* elapsedRealtimeMillis= */ 999L + AUTO_REVERT_THRESHOLD,
@@ -275,16 +280,16 @@
                             /* newZoneId= */ "Europe/London",
                             /* newConfidence= */ 1,
                             /* cause= */ "NO_REASON"));
-            expectedChangeEvent.setStatus(STATUS_UNTRACKED, SIGNAL_TYPE_NONE);
+            expectedTimeZoneChangeRecord.setStatus(STATUS_UNTRACKED, SIGNAL_TYPE_NONE);
 
-            mTimeZoneChangeTracker.process(expectedChangeEvent.getEvent());
-            TimeZoneChangeRecord trackedEvent3 =
+            mTimeZoneChangeTracker.process(expectedTimeZoneChangeRecord.getEvent());
+            TimeZoneChangeRecord timeZoneChangeRecord3 =
                     mTimeZoneChangeTracker.getLastTimeZoneChangeRecord();
 
             // The user manually changed the time zone within a short period of receiving the
             // notification, indicating that they rejected the automatic change of time zone
-            assertEquals(STATUS_REJECTED, trackedEvent2.getStatus());
-            assertEquals(expectedChangeEvent, trackedEvent3);
+            assertEquals(STATUS_REJECTED, timeZoneChangeRecord2.getStatus());
+            assertEquals(expectedTimeZoneChangeRecord, timeZoneChangeRecord3);
             assertEquals(2, mNotificationManager.getNotifications().size());
             mHandler.assertTotalMessagesEnqueued(3);
         }
@@ -293,12 +298,12 @@
         {
             // [START] Reset previous event
             enableNotificationsWithManualChangeTracking();
-            mTimeZoneChangeTracker.process(trackedEvent2.getEvent());
-            trackedEvent2 = mTimeZoneChangeTracker.getLastTimeZoneChangeRecord();
+            mTimeZoneChangeTracker.process(timeZoneChangeRecord2.getEvent());
+            timeZoneChangeRecord2 = mTimeZoneChangeTracker.getLastTimeZoneChangeRecord();
             disableTimeZoneAutoDetection();
             // [END] Reset previous event
 
-            expectedChangeEvent = new TimeZoneChangeRecord(
+            expectedTimeZoneChangeRecord = new TimeZoneChangeRecord(
                     /* id= */ 5,
                     new TimeZoneChangeEvent(
                             /* elapsedRealtimeMillis= */ 1001L + AUTO_REVERT_THRESHOLD,
@@ -310,15 +315,15 @@
                             /* newZoneId= */ "Europe/London",
                             /* newConfidence= */ 1,
                             /* cause= */ "NO_REASON"));
-            expectedChangeEvent.setStatus(STATUS_UNTRACKED, SIGNAL_TYPE_NONE);
+            expectedTimeZoneChangeRecord.setStatus(STATUS_UNTRACKED, SIGNAL_TYPE_NONE);
 
-            mTimeZoneChangeTracker.process(expectedChangeEvent.getEvent());
-            TimeZoneChangeRecord trackedEvent3 =
+            mTimeZoneChangeTracker.process(expectedTimeZoneChangeRecord.getEvent());
+            TimeZoneChangeRecord timeZoneChangeRecord3 =
                     mTimeZoneChangeTracker.getLastTimeZoneChangeRecord();
 
             // The user manually changed the time zone outside of the period we consider as a revert
-            assertEquals(STATUS_SUPERSEDED, trackedEvent2.getStatus());
-            assertEquals(expectedChangeEvent, trackedEvent3);
+            assertEquals(STATUS_SUPERSEDED, timeZoneChangeRecord2.getStatus());
+            assertEquals(expectedTimeZoneChangeRecord, timeZoneChangeRecord3);
             assertEquals(3, mNotificationManager.getNotifications().size());
             mHandler.assertTotalMessagesEnqueued(5);
         }
@@ -339,7 +344,7 @@
 
         enableNotificationsWithManualChangeTracking();
 
-        TimeZoneChangeRecord expectedChangeEvent = new TimeZoneChangeRecord(
+        TimeZoneChangeRecord expectedTimeZoneChangeRecord = new TimeZoneChangeRecord(
                 /* id= */ 1,
                 new TimeZoneChangeEvent(
                         /* elapsedRealtimeMillis= */ 0,
@@ -350,19 +355,20 @@
                         /* newZoneId= */ "Europe/London",
                         /* newConfidence= */ 1,
                         /* cause= */ "NO_REASON"));
-        expectedChangeEvent.setStatus(STATUS_UNKNOWN, SIGNAL_TYPE_UNKNOWN);
+        expectedTimeZoneChangeRecord.setStatus(STATUS_UNKNOWN, SIGNAL_TYPE_UNKNOWN);
 
         assertNull(mTimeZoneChangeTracker.getLastTimeZoneChangeRecord());
 
         // lastTrackedChangeEvent == null
-        mTimeZoneChangeTracker.process(expectedChangeEvent.getEvent());
-        TimeZoneChangeRecord trackedEvent1 = mTimeZoneChangeTracker.getLastTimeZoneChangeRecord();
+        mTimeZoneChangeTracker.process(expectedTimeZoneChangeRecord.getEvent());
+        TimeZoneChangeRecord timeZoneChangeRecord1 =
+                mTimeZoneChangeTracker.getLastTimeZoneChangeRecord();
 
-        assertEquals(expectedChangeEvent, trackedEvent1);
+        assertEquals(expectedTimeZoneChangeRecord, timeZoneChangeRecord1);
         assertEquals(1, mNotificationManager.getNotifications().size());
         mHandler.assertTotalMessagesEnqueued(1);
 
-        expectedChangeEvent = new TimeZoneChangeRecord(
+        expectedTimeZoneChangeRecord = new TimeZoneChangeRecord(
                 /* id= */ 3,
                 new TimeZoneChangeEvent(
                         /* elapsedRealtimeMillis= */ 1000L,
@@ -373,14 +379,15 @@
                         /* newZoneId= */ "Europe/Paris",
                         /* newConfidence= */ 1,
                         /* cause= */ "NO_REASON"));
-        expectedChangeEvent.setStatus(STATUS_UNKNOWN, SIGNAL_TYPE_UNKNOWN);
+        expectedTimeZoneChangeRecord.setStatus(STATUS_UNKNOWN, SIGNAL_TYPE_UNKNOWN);
 
         // lastTrackedChangeEvent != null
-        mTimeZoneChangeTracker.process(expectedChangeEvent.getEvent());
-        TimeZoneChangeRecord trackedEvent2 = mTimeZoneChangeTracker.getLastTimeZoneChangeRecord();
+        mTimeZoneChangeTracker.process(expectedTimeZoneChangeRecord.getEvent());
+        TimeZoneChangeRecord timeZoneChangeRecord2 =
+                mTimeZoneChangeTracker.getLastTimeZoneChangeRecord();
 
-        assertEquals(STATUS_SUPERSEDED, trackedEvent1.getStatus());
-        assertEquals(expectedChangeEvent, trackedEvent2);
+        assertEquals(STATUS_SUPERSEDED, timeZoneChangeRecord1.getStatus());
+        assertEquals(expectedTimeZoneChangeRecord, timeZoneChangeRecord2);
         assertEquals(2, mNotificationManager.getNotifications().size());
         mHandler.assertTotalMessagesEnqueued(2);
     }
@@ -400,7 +407,7 @@
 
         enableNotificationsWithManualChangeTracking();
 
-        TimeZoneChangeRecord expectedChangeEvent = new TimeZoneChangeRecord(
+        TimeZoneChangeRecord expectedTimeZoneChangeRecord = new TimeZoneChangeRecord(
                 /* id= */ 1,
                 new TimeZoneChangeEvent(
                         /* elapsedRealtimeMillis= */ 0,
@@ -411,15 +418,16 @@
                         /* newZoneId= */ "Europe/Rome",
                         /* newConfidence= */ 1,
                         /* cause= */ "NO_REASON"));
-        expectedChangeEvent.setStatus(STATUS_UNKNOWN, SIGNAL_TYPE_UNKNOWN);
+        expectedTimeZoneChangeRecord.setStatus(STATUS_UNKNOWN, SIGNAL_TYPE_UNKNOWN);
 
         assertNull(mTimeZoneChangeTracker.getLastTimeZoneChangeRecord());
 
         // lastTrackedChangeEvent == null
-        mTimeZoneChangeTracker.process(expectedChangeEvent.getEvent());
-        TimeZoneChangeRecord trackedEvent1 = mTimeZoneChangeTracker.getLastTimeZoneChangeRecord();
+        mTimeZoneChangeTracker.process(expectedTimeZoneChangeRecord.getEvent());
+        TimeZoneChangeRecord timeZoneChangeRecord1 =
+                mTimeZoneChangeTracker.getLastTimeZoneChangeRecord();
 
-        assertEquals(expectedChangeEvent, trackedEvent1);
+        assertEquals(expectedTimeZoneChangeRecord, timeZoneChangeRecord1);
         // No notification sent for the same UTC offset
         assertEquals(0, mNotificationManager.getNotifications().size());
         mHandler.assertTotalMessagesEnqueued(1);
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 9a01fa2..44232e7 100644
--- a/services/tests/timetests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/timetests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -63,7 +63,6 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
-import android.annotation.ElapsedRealtimeLong;
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.app.time.LocationTimeZoneAlgorithmStatus;
@@ -94,7 +93,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -114,6 +112,7 @@
     @Rule
     public final SetFlagsRule mSetFlagsRule = mClassRule.createSetFlagsRule();
 
+    private static final long ARBITRARY_CURRENT_TIME_MILLIS = 1735689600; // 2025-01-01 00:00:00
     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";
@@ -1220,7 +1219,7 @@
                 .build();
 
         Script script = new Script()
-                .initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS)
+                .initializeClock(ARBITRARY_CURRENT_TIME_MILLIS, ARBITRARY_ELAPSED_REALTIME_MILLIS)
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
                 .simulateConfigurationInternalChange(config)
                 .resetConfigurationTracking();
@@ -1420,7 +1419,7 @@
                 .build();
 
         Script script = new Script()
-                .initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS)
+                .initializeClock(ARBITRARY_CURRENT_TIME_MILLIS, ARBITRARY_ELAPSED_REALTIME_MILLIS)
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
                 .simulateConfigurationInternalChange(config)
                 .resetConfigurationTracking();
@@ -1591,7 +1590,7 @@
                 .build();
 
         Script script = new Script()
-                .initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS)
+                .initializeClock(ARBITRARY_CURRENT_TIME_MILLIS, ARBITRARY_ELAPSED_REALTIME_MILLIS)
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
                 .simulateConfigurationInternalChange(config)
                 .resetConfigurationTracking();
@@ -1666,7 +1665,7 @@
     @Test
     public void testGetTimeZoneState() {
         Script script = new Script()
-                .initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS)
+                .initializeClock(ARBITRARY_CURRENT_TIME_MILLIS, ARBITRARY_ELAPSED_REALTIME_MILLIS)
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
                 .simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED_GEO_DISABLED)
                 .resetConfigurationTracking();
@@ -1688,7 +1687,7 @@
     @Test
     public void testSetTimeZoneState() {
         Script script = new Script()
-                .initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS)
+                .initializeClock(ARBITRARY_CURRENT_TIME_MILLIS, ARBITRARY_ELAPSED_REALTIME_MILLIS)
                 .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
                 .simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED_GEO_DISABLED)
                 .resetConfigurationTracking();
@@ -1715,7 +1714,7 @@
     private void testConfirmTimeZone(ConfigurationInternal config) {
         String timeZoneId = "Europe/London";
         Script script = new Script()
-                .initializeClock(ARBITRARY_ELAPSED_REALTIME_MILLIS)
+                .initializeClock(ARBITRARY_CURRENT_TIME_MILLIS, ARBITRARY_ELAPSED_REALTIME_MILLIS)
                 .initializeTimeZoneSetting(timeZoneId, TIME_ZONE_CONFIDENCE_LOW)
                 .simulateConfigurationInternalChange(config)
                 .resetConfigurationTracking();
@@ -1902,97 +1901,6 @@
                 mFakeEnvironment.elapsedRealtimeMillis(), Arrays.asList(zoneIds));
     }
 
-    static class FakeEnvironment implements TimeZoneDetectorStrategyImpl.Environment {
-
-        private final TestState<String> mTimeZoneId = new TestState<>();
-        private final TestState<Integer> mTimeZoneConfidence = new TestState<>();
-        private final List<Runnable> mAsyncRunnables = new ArrayList<>();
-        private @ElapsedRealtimeLong long mElapsedRealtimeMillis;
-
-        FakeEnvironment() {
-            // Ensure the fake environment starts with the defaults a fresh device would.
-            initializeTimeZoneSetting("", TIME_ZONE_CONFIDENCE_LOW);
-        }
-
-        void initializeClock(@ElapsedRealtimeLong long elapsedRealtimeMillis) {
-            mElapsedRealtimeMillis = elapsedRealtimeMillis;
-        }
-
-        void initializeTimeZoneSetting(String zoneId, @TimeZoneConfidence int timeZoneConfidence) {
-            mTimeZoneId.init(zoneId);
-            mTimeZoneConfidence.init(timeZoneConfidence);
-        }
-
-        void incrementClock() {
-            mElapsedRealtimeMillis++;
-        }
-
-        @Override
-        public String getDeviceTimeZone() {
-            return mTimeZoneId.getLatest();
-        }
-
-        @Override
-        public int getDeviceTimeZoneConfidence() {
-            return mTimeZoneConfidence.getLatest();
-        }
-
-        @Override
-        public void setDeviceTimeZoneAndConfidence(
-                String zoneId, @TimeZoneConfidence int confidence, String logInfo) {
-            mTimeZoneId.set(zoneId);
-            mTimeZoneConfidence.set(confidence);
-        }
-
-        void assertTimeZoneNotChanged() {
-            mTimeZoneId.assertHasNotBeenSet();
-            mTimeZoneConfidence.assertHasNotBeenSet();
-        }
-
-        void assertTimeZoneChangedTo(String timeZoneId, @TimeZoneConfidence int confidence) {
-            mTimeZoneId.assertHasBeenSet();
-            mTimeZoneId.assertChangeCount(1);
-            mTimeZoneId.assertLatestEquals(timeZoneId);
-
-            mTimeZoneConfidence.assertHasBeenSet();
-            mTimeZoneConfidence.assertChangeCount(1);
-            mTimeZoneConfidence.assertLatestEquals(confidence);
-        }
-
-        void commitAllChanges() {
-            mTimeZoneId.commitLatest();
-            mTimeZoneConfidence.commitLatest();
-        }
-
-        @Override
-        @ElapsedRealtimeLong
-        public long elapsedRealtimeMillis() {
-            return mElapsedRealtimeMillis;
-        }
-
-        @Override
-        public void addDebugLogEntry(String logMsg) {
-            // No-op for tests
-        }
-
-        @Override
-        public void dumpDebugLog(PrintWriter printWriter) {
-            // No-op for tests
-        }
-
-        @Override
-        public void runAsync(Runnable runnable) {
-            mAsyncRunnables.add(runnable);
-        }
-
-        public void runAsyncRunnables() {
-            for (Runnable runnable : mAsyncRunnables) {
-                runnable.run();
-            }
-            mAsyncRunnables.clear();
-        }
-    }
-
     private void assertStateChangeNotificationsSent(
             TestStateChangeListener stateChangeListener, int expectedCount) {
         // The fake environment needs to be told to run posted work.
@@ -2013,8 +1921,8 @@
             return this;
         }
 
-        Script initializeClock(long elapsedRealtimeMillis) {
-            mFakeEnvironment.initializeClock(elapsedRealtimeMillis);
+        Script initializeClock(long currentTimeMillis, long elapsedRealtimeMillis) {
+            mFakeEnvironment.initializeClock(currentTimeMillis, elapsedRealtimeMillis);
             return this;
         }
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
index 4f5cdb7..1df8e3d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
@@ -64,6 +64,8 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.IntArray;
+import android.util.StatsEvent;
+import android.util.StatsEventTestUtils;
 import android.util.Xml;
 
 import androidx.test.runner.AndroidJUnit4;
@@ -71,9 +73,17 @@
 import com.android.internal.util.CollectionUtils;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
+import com.android.os.AtomsProto;
+import com.android.os.notification.NotificationBundlePreferences;
+import com.android.os.notification.NotificationExtensionAtoms;
+import com.android.os.notification.NotificationProtoEnums;
 import com.android.server.UiServiceTestCase;
 import com.android.server.notification.NotificationManagerService.NotificationAssistants;
 
+import com.google.protobuf.CodedInputStream;
+import com.google.protobuf.CodedOutputStream;
+import com.google.protobuf.ExtensionRegistryLite;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -120,6 +130,8 @@
 
     ComponentName mCn = new ComponentName("a", "b");
 
+    private ExtensionRegistryLite mRegistry;
+
 
     // Helper function to hold mApproved lock, avoid GuardedBy lint errors
     private boolean isUserSetServicesEmpty(NotificationAssistants assistant, int userId) {
@@ -204,6 +216,8 @@
         when(mUserProfiles.getCurrentProfileIds()).thenReturn(profileIds);
         when(mNm.isNASMigrationDone(anyInt())).thenReturn(true);
         when(mNm.canUseManagedServices(any(), anyInt(), any())).thenReturn(true);
+        mRegistry = ExtensionRegistryLite.newInstance();
+        NotificationExtensionAtoms.registerAllExtensions(mRegistry);
     }
 
     @Test
@@ -749,6 +763,28 @@
     }
 
     @Test
+    @EnableFlags({android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION,
+            android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI})
+    public void testGetPackagesWithKeyTypeAdjustmentSettings() throws Exception {
+        String pkg = "my.package";
+        String pkg2 = "my.package.2";
+        setDefaultAllowedAdjustmentKeyTypes(mAssistants);
+        assertThat(mAssistants.isTypeAdjustmentAllowedForPackage(pkg, TYPE_PROMOTION)).isTrue();
+        assertThat(mAssistants.getPackagesWithKeyTypeAdjustmentSettings()).isEmpty();
+
+        mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, TYPE_PROMOTION, true);
+        assertThat(mAssistants.getPackagesWithKeyTypeAdjustmentSettings())
+                .containsExactly(pkg);
+        mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg, TYPE_PROMOTION, false);
+        assertThat(mAssistants.getPackagesWithKeyTypeAdjustmentSettings())
+                .containsExactly(pkg);
+        mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg2, TYPE_NEWS, true);
+        mAssistants.setAssistantAdjustmentKeyTypeStateForPackage(pkg2, TYPE_PROMOTION, false);
+        assertThat(mAssistants.getPackagesWithKeyTypeAdjustmentSettings())
+                .containsExactly(pkg, pkg2);
+    }
+
+    @Test
     @EnableFlags(android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI)
     public void testSetAssistantAdjustmentKeyTypeStateForPackage_usesGlobalDefault() {
         String pkg = "my.package";
@@ -892,4 +928,88 @@
         assertThat(mAssistants.getAllowedAdjustmentKeyTypesForPackage(pkg)).asList()
                 .containsExactly(TYPE_NEWS, TYPE_PROMOTION, TYPE_SOCIAL_MEDIA);
     }
+
+    @Test
+    @SuppressWarnings("GuardedBy")
+    @EnableFlags({android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION,
+            android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI})
+    public void testPullBundlePreferencesStats_fillsOutStatsEvent()
+            throws Exception {
+        // Create the current user and enable the package
+        int userId = ActivityManager.getCurrentUser();
+        mAssistants.loadDefaultsFromConfig(true);
+        mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true,
+                true, true);
+        ManagedServices.ManagedServiceInfo info =
+                mAssistants.new ManagedServiceInfo(null, mCn, userId, false, null, 35, 2345256);
+
+        // Ensure bundling is enabled
+        mAssistants.setAdjustmentTypeSupportedState(info, Adjustment.KEY_TYPE, true);
+        // Enable these specific bundle types
+        mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_PROMOTION, false);
+        mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_NEWS, true);
+        mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_CONTENT_RECOMMENDATION, true);
+
+        // When pullBundlePreferencesStats is run with the given preferences
+        ArrayList<StatsEvent> events = new ArrayList<>();
+        mAssistants.pullBundlePreferencesStats(events);
+
+        // The StatsEvent is filled out with the expected NotificationBundlePreferences values.
+        assertThat(events.size()).isEqualTo(1);
+        AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(events.get(0));
+
+        // The returned atom does not have external extensions registered.
+        // So we serialize and then deserialize with extensions registered.
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        CodedOutputStream codedos = CodedOutputStream.newInstance(outputStream);
+        atom.writeTo(codedos);
+        codedos.flush();
+
+        ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
+        CodedInputStream codedis = CodedInputStream.newInstance(inputStream);
+        atom = AtomsProto.Atom.parseFrom(codedis, mRegistry);
+        assertTrue(atom.hasExtension(NotificationExtensionAtoms.notificationBundlePreferences));
+        NotificationBundlePreferences p =
+                atom.getExtension(NotificationExtensionAtoms.notificationBundlePreferences);
+        assertThat(p.getBundlesAllowed()).isTrue();
+        assertThat(p.getAllowedBundleTypes(0).getNumber())
+                .isEqualTo(NotificationProtoEnums.TYPE_NEWS);
+        assertThat(p.getAllowedBundleTypes(1).getNumber())
+                .isEqualTo(NotificationProtoEnums.TYPE_CONTENT_RECOMMENDATION);
+
+        // Disable the top-level bundling setting
+        mAssistants.setAdjustmentTypeSupportedState(info, Adjustment.KEY_TYPE, false);
+        // Enable these specific bundle types
+        mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_PROMOTION, true);
+        mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_NEWS, false);
+        mAssistants.setAssistantAdjustmentKeyTypeState(TYPE_CONTENT_RECOMMENDATION, true);
+
+        ArrayList<StatsEvent> eventsDisabled = new ArrayList<>();
+        mAssistants.pullBundlePreferencesStats(eventsDisabled);
+
+        // The StatsEvent is filled out with the expected NotificationBundlePreferences values.
+        assertThat(eventsDisabled.size()).isEqualTo(1);
+        AtomsProto.Atom atomDisabled = StatsEventTestUtils.convertToAtom(eventsDisabled.get(0));
+
+        // The returned atom does not have external extensions registered.
+        // So we serialize and then deserialize with extensions registered.
+        outputStream = new ByteArrayOutputStream();
+        codedos = CodedOutputStream.newInstance(outputStream);
+        atomDisabled.writeTo(codedos);
+        codedos.flush();
+
+        inputStream = new ByteArrayInputStream(outputStream.toByteArray());
+        codedis = CodedInputStream.newInstance(inputStream);
+        atomDisabled = AtomsProto.Atom.parseFrom(codedis, mRegistry);
+        assertTrue(atomDisabled.hasExtension(NotificationExtensionAtoms
+                .notificationBundlePreferences));
+
+        NotificationBundlePreferences p2 =
+                atomDisabled.getExtension(NotificationExtensionAtoms.notificationBundlePreferences);
+        assertThat(p2.getBundlesAllowed()).isFalse();
+        assertThat(p2.getAllowedBundleTypes(0).getNumber())
+                .isEqualTo(NotificationProtoEnums.TYPE_PROMOTION);
+        assertThat(p2.getAllowedBundleTypes(1).getNumber())
+                .isEqualTo(NotificationProtoEnums.TYPE_CONTENT_RECOMMENDATION);
+    }
 }
\ No newline at end of file
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index e43b28b..fdb6a68 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -24,6 +24,7 @@
 import static android.app.ActivityManagerInternal.ServiceNotificationPolicy.SHOW_IMMEDIATELY;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.Flags.FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS;
+import static android.app.Flags.FLAG_REDACT_SENSITIVE_CONTENT_NOTIFICATIONS_ON_LOCKSCREEN;
 import static android.app.Flags.FLAG_SORT_SECTION_BY_TIME;
 import static android.app.Notification.EXTRA_ALLOW_DURING_SETUP;
 import static android.app.Notification.EXTRA_PICTURE;
@@ -251,6 +252,9 @@
 import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.media.session.MediaSession;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
@@ -475,6 +479,8 @@
     @Mock
     private PowerManager mPowerManager;
     @Mock
+    private ConnectivityManager mConnectivityManager;
+    @Mock
     private LightsManager mLightsManager;
     private final ArrayList<WakeLock> mAcquiredWakeLocks = new ArrayList<>();
     private final TestPostNotificationTrackerFactory mPostNotificationTrackerFactory =
@@ -765,6 +771,8 @@
         mActivityIntentImmutable = spy(PendingIntent.getActivity(mContext, 0,
                 new Intent().setPackage(mPkg), FLAG_IMMUTABLE));
 
+        when(mConnectivityManager.getActiveNetwork()).thenReturn(null);
+
         initNMS();
     }
 
@@ -798,7 +806,7 @@
                 mAppOpsManager, mUm, mHistoryManager, mStatsManager,
                 mAmi, mToastRateLimiter, mPermissionHelper, mock(UsageStatsManagerInternal.class),
                 mTelecomManager, mLogger, mTestFlagResolver, mPermissionManager,
-                mPowerManager, mPostNotificationTrackerFactory);
+                mPowerManager, mConnectivityManager, mPostNotificationTrackerFactory);
 
         mService.setAttentionHelper(mAttentionHelper);
         mService.setLockPatternUtils(mock(LockPatternUtils.class));
@@ -14314,7 +14322,8 @@
         mService.addNotification(pkgB);
 
         mService.setIsVisibleToListenerReturnValue(true);
-        NotificationRankingUpdate nru = mService.makeRankingUpdateLocked(null);
+        ManagedServices.ManagedServiceInfo info = mock(ManagedServices.ManagedServiceInfo.class);
+        NotificationRankingUpdate nru = mService.makeRankingUpdateLocked(info);
         assertEquals(2, nru.getRankingMap().getOrderedKeys().length);
 
         // when only user 0 entering the lockdown mode, its notification will be suppressed.
@@ -14324,7 +14333,7 @@
         assertTrue(mStrongAuthTracker.isInLockDownMode(0));
         assertFalse(mStrongAuthTracker.isInLockDownMode(1));
 
-        nru = mService.makeRankingUpdateLocked(null);
+        nru = mService.makeRankingUpdateLocked(info);
         assertEquals(1, nru.getRankingMap().getOrderedKeys().length);
 
         // User 0 exits lockdown mode. Its notification will be resumed.
@@ -14333,7 +14342,7 @@
         assertFalse(mStrongAuthTracker.isInLockDownMode(0));
         assertFalse(mStrongAuthTracker.isInLockDownMode(1));
 
-        nru = mService.makeRankingUpdateLocked(null);
+        nru = mService.makeRankingUpdateLocked(info);
         assertEquals(2, nru.getRankingMap().getOrderedKeys().length);
     }
 
@@ -14365,13 +14374,119 @@
         assertEquals(0, ranking2.getSmartReplies().size());
     }
 
+    private NotificationRecord getSensitiveNotificationRecord() {
+        NotificationRecord record = new NotificationRecord(mContext,
+                generateSbn("a", 1000, 9, 0), mTestNotificationChannel);
+        Bundle signals = new Bundle();
+        signals.putBoolean(Adjustment.KEY_SENSITIVE_CONTENT, true);
+        Adjustment adjustment = new Adjustment("a", record.getKey(), signals, "", 0);
+        record.addAdjustment(adjustment);
+        record.applyAdjustments();
+        return record;
+    }
+
+    @Test
+    public void testMakeRankingUpdate_clearsHasSensitiveContentIfConnectedToWifi() {
+        mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS,
+                FLAG_REDACT_SENSITIVE_CONTENT_NOTIFICATIONS_ON_LOCKSCREEN);
+        when(mConnectivityManager.getActiveNetwork()).thenReturn(mock(Network.class));
+        when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(
+                new NetworkCapabilities.Builder()
+                        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                        .build()
+        );
+        mService.updateWifiConnectionState();
+        when(mListeners.hasSensitiveContent(any())).thenReturn(true);
+        NotificationRecord pkgA = new NotificationRecord(mContext,
+                generateSbn("a", 1000, 9, 0), mTestNotificationChannel);
+        mService.addNotification(pkgA);
+        ManagedServices.ManagedServiceInfo info = mock(ManagedServices.ManagedServiceInfo.class);
+        info.isSystemUi = true;
+        when(info.enabledAndUserMatches(anyInt())).thenReturn(true);
+        when(info.isSameUser(anyInt())).thenReturn(true);
+        NotificationRankingUpdate nru = mService.makeRankingUpdateLocked(info);
+        NotificationListenerService.Ranking ranking =
+                nru.getRankingMap().getRawRankingObject(pkgA.getSbn().getKey());
+        assertFalse(ranking.hasSensitiveContent());
+    }
+
+    @Test
+    public void testMakeRankingUpdate_doesntClearHasSensitiveContentIfNotConnectedToWifi() {
+        mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS,
+                FLAG_REDACT_SENSITIVE_CONTENT_NOTIFICATIONS_ON_LOCKSCREEN);
+        when(mConnectivityManager.getActiveNetwork()).thenReturn(mock(Network.class));
+        when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(
+                new NetworkCapabilities.Builder()
+                        .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                        .build()
+        );
+        mService.updateWifiConnectionState();
+        when(mListeners.hasSensitiveContent(any())).thenReturn(true);
+        NotificationRecord record = getSensitiveNotificationRecord();
+        mService.addNotification(record);
+        ManagedServices.ManagedServiceInfo info = mock(ManagedServices.ManagedServiceInfo.class);
+        info.isSystemUi = true;
+        when(info.enabledAndUserMatches(anyInt())).thenReturn(true);
+        when(info.isSameUser(anyInt())).thenReturn(true);
+        NotificationRankingUpdate nru = mService.makeRankingUpdateLocked(info);
+        NotificationListenerService.Ranking ranking =
+                nru.getRankingMap().getRawRankingObject(record.getSbn().getKey());
+        assertTrue(ranking.hasSensitiveContent());
+    }
+
+    @Test
+    public void testMakeRankingUpdate_doesntClearHasSensitiveContentIfNotSysUi() {
+        mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
+        mSetFlagsRule.disableFlags(FLAG_REDACT_SENSITIVE_CONTENT_NOTIFICATIONS_ON_LOCKSCREEN);
+        when(mConnectivityManager.getActiveNetwork()).thenReturn(mock(Network.class));
+        when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(
+                new NetworkCapabilities.Builder()
+                        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                        .build()
+        );
+        mService.updateWifiConnectionState();
+        when(mListeners.hasSensitiveContent(any())).thenReturn(true);
+        NotificationRecord record = getSensitiveNotificationRecord();
+        mService.addNotification(record);
+        ManagedServices.ManagedServiceInfo info = mock(ManagedServices.ManagedServiceInfo.class);
+        when(info.enabledAndUserMatches(anyInt())).thenReturn(true);
+        when(info.isSameUser(anyInt())).thenReturn(true);
+        NotificationRankingUpdate nru = mService.makeRankingUpdateLocked(info);
+        NotificationListenerService.Ranking ranking =
+                nru.getRankingMap().getRawRankingObject(record.getSbn().getKey());
+        assertTrue(ranking.hasSensitiveContent());
+    }
+
+    @Test
+    public void testMakeRankingUpdate_doesntClearHasSensitiveContentIfFlagDisabled() {
+        mSetFlagsRule.enableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
+        mSetFlagsRule.disableFlags(FLAG_REDACT_SENSITIVE_CONTENT_NOTIFICATIONS_ON_LOCKSCREEN);
+        when(mConnectivityManager.getActiveNetwork()).thenReturn(mock(Network.class));
+        when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(
+                new NetworkCapabilities.Builder()
+                        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                        .build()
+        );
+        mService.updateWifiConnectionState();
+        when(mListeners.hasSensitiveContent(any())).thenReturn(true);
+        NotificationRecord record = getSensitiveNotificationRecord();
+        mService.addNotification(record);
+        ManagedServices.ManagedServiceInfo info = mock(ManagedServices.ManagedServiceInfo.class);
+        info.isSystemUi = true;
+        when(info.enabledAndUserMatches(anyInt())).thenReturn(true);
+        when(info.isSameUser(anyInt())).thenReturn(true);
+        NotificationRankingUpdate nru = mService.makeRankingUpdateLocked(info);
+        NotificationListenerService.Ranking ranking =
+                nru.getRankingMap().getRawRankingObject(record.getSbn().getKey());
+        assertTrue(ranking.hasSensitiveContent());
+    }
+
     @Test
     public void testMakeRankingUpdate_doestntRedactIfFlagDisabled() {
         mSetFlagsRule.disableFlags(FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS);
         when(mListeners.isUidTrusted(anyInt())).thenReturn(false);
         when(mListeners.hasSensitiveContent(any())).thenReturn(true);
-        NotificationRecord pkgA = new NotificationRecord(mContext,
-                generateSbn("a", 1000, 9, 0), mTestNotificationChannel);
+        NotificationRecord pkgA = getSensitiveNotificationRecord();
         addSmartActionsAndReplies(pkgA);
 
         mService.addNotification(pkgA);
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 f41805d..704b580 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -19,6 +19,7 @@
 import static android.app.AppOpsManager.MODE_DEFAULT;
 import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
 import static android.app.Flags.FLAG_MODES_UI;
+import static android.app.Flags.FLAG_NOTIFICATION_CLASSIFICATION_UI;
 import static android.app.Notification.VISIBILITY_PRIVATE;
 import static android.app.Notification.VISIBILITY_SECRET;
 import static android.app.NotificationChannel.ALLOW_BUBBLE_ON;
@@ -115,6 +116,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
 import android.content.pm.Signature;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
@@ -161,6 +163,7 @@
 import com.android.os.AtomsProto;
 import com.android.os.AtomsProto.PackageNotificationChannelPreferences;
 import com.android.os.AtomsProto.PackageNotificationPreferences;
+import com.android.os.notification.NotificationProtoEnums;
 import com.android.server.UiServiceTestCase;
 import com.android.server.notification.PermissionHelper.PackagePermission;
 import com.android.server.uri.UriGrantsManagerInternal;
@@ -256,7 +259,8 @@
     public static List<FlagsParameterization> getParams() {
         return FlagsParameterization.allCombinationsOf(
                 android.app.Flags.FLAG_API_RICH_ONGOING,
-                FLAG_NOTIFICATION_CLASSIFICATION, FLAG_MODES_UI);
+                FLAG_NOTIFICATION_CLASSIFICATION, FLAG_NOTIFICATION_CLASSIFICATION_UI,
+                FLAG_MODES_UI);
     }
 
     public PreferencesHelperTest(FlagsParameterization flags) {
@@ -6135,6 +6139,7 @@
     }
 
     @Test
+    @DisableFlags({FLAG_NOTIFICATION_CLASSIFICATION_UI})
     public void testPullPackagePreferencesStats_postPermissionMigration()
             throws InvalidProtocolBufferException {
         // make sure there's at least one channel for each package we want to test
@@ -6155,6 +6160,11 @@
         mHelper.canShowBadge(PKG_O, UID_O);
         mHelper.canShowBadge(PKG_P, UID_P);
 
+        ArrayList<StatsEvent> events = new ArrayList<>();
+
+        mHelper.pullPackagePreferencesStats(events, appPermissions,
+                new ArrayMap<String, Set<Integer>>());
+
         // expected output. format: uid -> importance, as only uid (and not package name)
         // is in PackageNotificationPreferences
         ArrayMap<Integer, Pair<Integer, Boolean>> expected = new ArrayMap<>();
@@ -6162,9 +6172,6 @@
         expected.put(UID_O, new Pair<>(IMPORTANCE_NONE, true));         // banned by permissions
         expected.put(UID_P, new Pair<>(IMPORTANCE_UNSPECIFIED, false)); // default: unspecified
 
-        ArrayList<StatsEvent> events = new ArrayList<>();
-        mHelper.pullPackagePreferencesStats(events, appPermissions);
-
         assertEquals("total number of packages", 3, events.size());
         for (StatsEvent ev : events) {
             AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
@@ -6180,6 +6187,74 @@
     }
 
     @Test
+    @EnableFlags({FLAG_NOTIFICATION_CLASSIFICATION, FLAG_NOTIFICATION_CLASSIFICATION_UI})
+    public void testPullPackagePreferencesStats_createsExpectedStatsEvents()
+            throws InvalidProtocolBufferException {
+        // make sure there's at least one channel for each package we want to test
+        NotificationChannel channelA = new NotificationChannel("a", "a", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channelA, true, false,
+                UID_N_MR1, false);
+        NotificationChannel channelB = new NotificationChannel("b", "b", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_O, UID_O, channelB, true, false, UID_O, false);
+        NotificationChannel channelC = new NotificationChannel("c", "c", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_P, UID_P, channelC, true, false, UID_P, false);
+
+        // build a collection of app permissions that should be passed in and used
+        ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions = new ArrayMap<>();
+        pkgPermissions.put(new Pair<>(UID_N_MR1, PKG_N_MR1), new Pair<>(true, false));
+        pkgPermissions.put(new Pair<>(UID_O, PKG_O), new Pair<>(false, true)); // in local prefs
+
+        // local preferences
+        mHelper.canShowBadge(PKG_O, UID_O);
+        mHelper.canShowBadge(PKG_P, UID_P);
+
+        // Sets bundles_allowed to true for these packages.
+        ArrayMap<String, Set<Integer>> packageSpecificAdjustmentKeyTypes = new ArrayMap<>();
+        Set<Integer> nMr1BundlesSet = new ArraySet<Integer>();
+        nMr1BundlesSet.add(TYPE_NEWS);
+        nMr1BundlesSet.add(TYPE_SOCIAL_MEDIA);
+        packageSpecificAdjustmentKeyTypes.put(PKG_N_MR1, nMr1BundlesSet);
+        Set<Integer> pBundlesSet = new ArraySet<Integer>();
+        packageSpecificAdjustmentKeyTypes.put(PKG_P, pBundlesSet);
+
+        ArrayList<StatsEvent> events = new ArrayList<>();
+
+        mHelper.pullPackagePreferencesStats(events, pkgPermissions,
+                packageSpecificAdjustmentKeyTypes);
+
+        assertEquals("total number of packages", 3, events.size());
+
+        AtomsProto.Atom atom0 = StatsEventTestUtils.convertToAtom(events.get(0));
+        assertTrue(atom0.hasPackageNotificationPreferences());
+        PackageNotificationPreferences p0 = atom0.getPackageNotificationPreferences();
+        assertThat(p0.getUid()).isEqualTo(UID_O);
+        assertThat(p0.getImportance()).isEqualTo(IMPORTANCE_NONE); // banned by permissions
+        assertThat(p0.getUserSetImportance()).isTrue();
+        assertThat(p0.getAllowedBundleTypesList()).hasSize(0);
+
+        AtomsProto.Atom atom1 = StatsEventTestUtils.convertToAtom(events.get(1));
+        assertTrue(atom1.hasPackageNotificationPreferences());
+        PackageNotificationPreferences p1 = atom1.getPackageNotificationPreferences();
+        assertThat(p1.getUid()).isEqualTo(UID_N_MR1);
+        assertThat(p1.getImportance()).isEqualTo(IMPORTANCE_DEFAULT);
+        assertThat(p1.getUserSetImportance()).isFalse();
+        assertThat(p1.getAllowedBundleTypesList()).hasSize(2);
+
+        assertThat(p1.getAllowedBundleTypes(0).getNumber())
+                .isEqualTo(NotificationProtoEnums.TYPE_SOCIAL_MEDIA);
+        assertThat(p1.getAllowedBundleTypes(1).getNumber())
+                .isEqualTo(NotificationProtoEnums.TYPE_NEWS);
+
+        AtomsProto.Atom atom2 = StatsEventTestUtils.convertToAtom(events.get(2));
+        assertTrue(atom2.hasPackageNotificationPreferences());
+        PackageNotificationPreferences p2 = atom2.getPackageNotificationPreferences();
+        assertThat(p2.getUid()).isEqualTo(UID_P);
+        assertThat(p2.getImportance()).isEqualTo(IMPORTANCE_UNSPECIFIED); // default: unspecified
+        assertThat(p2.getUserSetImportance()).isFalse();
+        assertThat(p2.getAllowedBundleTypesList()).hasSize(0);
+    }
+
+    @Test
     public void testUnlockNotificationChannelImportance() {
         NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_LOW);
         mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, false, UID_O, false);
@@ -6760,6 +6835,41 @@
         assertThat(mHelper.hasCacheBeenInvalidated()).isFalse();
     }
 
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+    public void testGetNotificationChannels_createIfNeeded() {
+        // Test setup hasn't created any channels or read package preferences yet.
+        // If we ask for notification channels _without_ creating, we should get no result.
+        ParceledListSlice<NotificationChannel> channels = mHelper.getNotificationChannels(PKG_N_MR1,
+                UID_N_MR1, false, false, /* createPrefsIfNeeded= */ false);
+        assertThat(channels.getList().size()).isEqualTo(0);
+
+        // If we ask it to create package preferences, we expect the default channel to be created
+        // for N_MR1.
+        channels = mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false,
+                false, /* createPrefsIfNeeded= */ true);
+        assertThat(channels.getList().size()).isEqualTo(1);
+        assertThat(channels.getList().getFirst().getId()).isEqualTo(
+                NotificationChannel.DEFAULT_CHANNEL_ID);
+    }
+
+    @Test
+    @DisableFlags(android.app.Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS)
+    public void testGetNotificationChannels_neverCreatesWhenFlagOff() {
+        ParceledListSlice<NotificationChannel> channels;
+        try {
+            channels = mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false,
+                    false, /* createPrefsIfNeeded= */ true);
+        } catch (Exception e) {
+            // Slog.wtf kicks in, presumably
+        } finally {
+            channels = mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false,
+                    false, /* createPrefsIfNeeded= */ false);
+            assertThat(channels.getList().size()).isEqualTo(0);
+        }
+
+    }
+
     // 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.
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
index c1bb3e7..82d87d4 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
@@ -47,6 +47,7 @@
 import android.content.Context;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
 import android.os.Looper;
 import android.os.PowerManager;
 import android.os.UserHandle;
@@ -171,6 +172,7 @@
                     mock(NotificationChannelLogger.class), new TestableFlagResolver(),
                     mock(PermissionManager.class),
                     mock(PowerManager.class),
+                    mock(ConnectivityManager.class),
                     new NotificationManagerService.PostNotificationTrackerFactory() {});
         } catch (SecurityException e) {
             if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 194d48a..767c02b 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -109,6 +109,7 @@
 import com.android.server.LocalServices;
 import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
 import com.android.server.pm.BackgroundUserSoundNotifier;
+import com.android.server.pm.UserManagerService;
 import com.android.server.vibrator.VibrationSession.Status;
 
 import org.junit.After;
@@ -896,8 +897,8 @@
     }
 
     @Test
-    @EnableFlags(android.multiuser.Flags.FLAG_ADD_UI_FOR_SOUNDS_FROM_BACKGROUND_USERS)
     public void vibrate_thenFgUserRequestsMute_getsCancelled() throws Throwable {
+        assumeTrue(UserManagerService.shouldShowNotificationForBackgroundUserSounds());
         mockVibrators(1);
         VibratorManagerService service = createSystemReadyService();
 
@@ -2758,8 +2759,8 @@
     }
 
     @Test
-    @EnableFlags(android.multiuser.Flags.FLAG_ADD_UI_FOR_SOUNDS_FROM_BACKGROUND_USERS)
     public void onExternalVibration_thenFgUserRequestsMute_doNotCancelVibration() throws Throwable {
+        assumeTrue(UserManagerService.shouldShowNotificationForBackgroundUserSounds());
         mockVibrators(1);
         mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
         VibratorManagerService service = createSystemReadyService();
diff --git a/services/tests/wmtests/Android.bp b/services/tests/wmtests/Android.bp
index 132f95b..b328fc2 100644
--- a/services/tests/wmtests/Android.bp
+++ b/services/tests/wmtests/Android.bp
@@ -11,19 +11,46 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
-// Include all test java files.
+filegroup {
+    name: "wmtests-support-sources",
+    srcs: [
+        "src/com/android/server/wm/WindowManagerServiceTestSupport.kt",
+    ],
+    path: "src",
+    visibility: ["//visibility:private"],
+}
+
+java_library {
+    name: "wmtests-support",
+    srcs: [":wmtests-support-sources"],
+    static_libs: [
+        "com.android.window.flags.window-aconfig-java",
+        "kotlin-stdlib",
+        "services.core",
+    ],
+    lint: {
+        test: true,
+    },
+    visibility: [
+        "//frameworks/base/services/tests/wmtests",
+        "//frameworks/opt/car/services/updatableServices/tests",
+    ],
+}
+
+// Include all test files, but exclude test support files.
 filegroup {
     name: "wmtests-sources",
-    srcs: [
-        "src/**/*.java",
-    ],
+    srcs: ["src/**/*.java"],
+    exclude_srcs: [":wmtests-support-sources"],
+    path: "src",
+    visibility: ["//visibility:private"],
 }
 
 java_genrule {
     name: "wmtests.protologsrc",
     srcs: [
-        ":protolog-impl",
         ":protolog-groups",
+        ":protolog-impl",
         ":wmtests-sources",
     ],
     tools: ["protologtool"],
@@ -52,33 +79,34 @@
     ],
 
     static_libs: [
-        "frameworks-base-testutils",
-        "services.core",
-        "service-permission.stubs.system_server",
-        "androidx.test.runner",
+        "CtsSurfaceValidatorLib",
+        "android.view.inputmethod.flags-aconfig-java",
         "androidx.test.rules",
+        "androidx.test.runner",
+        "com.android.window.flags.window-aconfig-java",
+        "flag-junit",
         "flickerlib",
+        "frameworks-base-testutils",
+        "hamcrest-library",
         "junit-params",
+        "mockito-kotlin2",
         "mockito-target-extended-minus-junit4",
+        "platform-compat-test-rules",
         "platform-test-annotations",
+        "service-permission.stubs.system_server",
+        "service-sdksandbox.impl",
+        "services.core",
         "servicestests-utils",
+        "testables",
         "testng",
         "truth",
-        "testables",
-        "hamcrest-library",
-        "flag-junit",
-        "platform-compat-test-rules",
-        "CtsSurfaceValidatorLib",
-        "service-sdksandbox.impl",
-        "com.android.window.flags.window-aconfig-java",
-        "android.view.inputmethod.flags-aconfig-java",
-        "flag-junit",
+        "wmtests-support",
     ],
 
     libs: [
         "android.hardware.power-V1-java",
-        "android.test.mock.stubs.system",
         "android.test.base.stubs.system",
+        "android.test.mock.stubs.system",
         "android.test.runner.stubs.system",
     ],
 
@@ -94,8 +122,8 @@
 
     platform_apis: true,
     test_suites: [
-        "device-tests",
         "automotive-tests",
+        "device-tests",
     ],
 
     certificate: "platform",
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 6fad82b..c88d515 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -692,7 +692,7 @@
 
         // Asserts fixed orientation request is not ignored, and the orientation is changed.
         assertNotEquals(activityCurOrientation, activity.getConfiguration().orientation);
-        assertTrue(activity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertTrue(activity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
     }
 
@@ -721,13 +721,13 @@
         assertEquals(ORIENTATION_PORTRAIT, activity.getConfiguration().orientation);
 
         // Clear size compat.
-        activity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
+        activity.mAppCompatController.getSizeCompatModePolicy().clearSizeCompatMode();
         activity.ensureActivityConfiguration();
         mDisplayContent.sendNewConfiguration();
 
         // Relaunching the app should still respect the orientation request.
         assertEquals(ORIENTATION_PORTRAIT, activity.getConfiguration().orientation);
-        assertTrue(activity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertTrue(activity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
     }
 
@@ -742,7 +742,7 @@
         assertEquals(ORIENTATION_LANDSCAPE, activity.getTask().getConfiguration().orientation);
         // The app should be letterboxed.
         assertEquals(ORIENTATION_PORTRAIT, activity.getConfiguration().orientation);
-        assertTrue(activity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertTrue(activity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
     }
 
@@ -755,7 +755,7 @@
         assertEquals(ORIENTATION_LANDSCAPE, activity.getTask().getConfiguration().orientation);
         // Activity is not letterboxed.
         assertEquals(ORIENTATION_LANDSCAPE, activity.getConfiguration().orientation);
-        assertFalse(activity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertFalse(activity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
     }
 
@@ -770,7 +770,7 @@
         assertEquals(ORIENTATION_LANDSCAPE, activity.getTask().getConfiguration().orientation);
         // Activity is not letterboxed.
         assertEquals(ORIENTATION_LANDSCAPE, activity.getConfiguration().orientation);
-        assertFalse(activity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertFalse(activity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
     }
 
@@ -785,7 +785,7 @@
         assertEquals(ORIENTATION_LANDSCAPE, activity.getTask().getConfiguration().orientation);
         // Activity is not letterboxed.
         assertEquals(ORIENTATION_LANDSCAPE, activity.getConfiguration().orientation);
-        assertFalse(activity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertFalse(activity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
     }
 
@@ -3800,7 +3800,7 @@
                 .setResizeMode(RESIZE_MODE_RESIZEABLE)
                 .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
                 .build();
-        activity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
+        activity.mAppCompatController.getSizeCompatModePolicy().clearSizeCompatMode();
         return activity;
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
index 00c9691..018ea58 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
@@ -180,7 +180,7 @@
 
     void setLetterboxedForFixedOrientationAndAspectRatio(boolean enabled) {
         doReturn(enabled).when(mActivityStack.top().mAppCompatController
-                .getAppCompatAspectRatioPolicy()).isLetterboxedForFixedOrientationAndAspectRatio();
+                .getAspectRatioPolicy()).isLetterboxedForFixedOrientationAndAspectRatio();
     }
 
     void enableFullscreenCameraCompatTreatmentForTopActivity(boolean enabled) {
@@ -213,7 +213,7 @@
 
     void setShouldApplyUserMinAspectRatioOverride(boolean enabled) {
         doReturn(enabled).when(mActivityStack.top().mAppCompatController
-                .getAppCompatAspectRatioOverrides()).shouldApplyUserMinAspectRatioOverride();
+                .getAspectRatioOverrides()).shouldApplyUserMinAspectRatioOverride();
     }
 
     void setShouldCreateCompatDisplayInsets(boolean enabled) {
@@ -226,17 +226,17 @@
 
     void setShouldApplyUserFullscreenOverride(boolean enabled) {
         doReturn(enabled).when(mActivityStack.top().mAppCompatController
-                .getAppCompatAspectRatioOverrides()).shouldApplyUserFullscreenOverride();
+                .getAspectRatioOverrides()).shouldApplyUserFullscreenOverride();
     }
 
     void setGetUserMinAspectRatioOverrideCode(@UserMinAspectRatio int overrideCode) {
         doReturn(overrideCode).when(mActivityStack.top().mAppCompatController
-                .getAppCompatAspectRatioOverrides()).getUserMinAspectRatioOverrideCode();
+                .getAspectRatioOverrides()).getUserMinAspectRatioOverrideCode();
     }
 
     void setGetUserMinAspectRatioOverrideValue(float overrideValue) {
         doReturn(overrideValue).when(mActivityStack.top().mAppCompatController
-                .getAppCompatAspectRatioOverrides()).getUserMinAspectRatio();
+                .getAspectRatioOverrides()).getUserMinAspectRatio();
     }
 
     void setIgnoreOrientationRequest(boolean enabled) {
@@ -525,7 +525,7 @@
             activity.setRequestedOrientation(screenOrientation);
         }
         // Make sure to use the provided configuration to construct the size compat fields.
-        activity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
+        activity.mAppCompatController.getSizeCompatModePolicy().clearSizeCompatMode();
         activity.ensureActivityConfiguration();
         // Make sure the display configuration reflects the change of activity.
         if (activity.mDisplayContent.updateOrientation()) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java
index 14ef913..f29cbc6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatAspectRatioOverridesTest.java
@@ -413,7 +413,7 @@
         @Override
         void onPostActivityCreation(@NonNull ActivityRecord activity) {
             super.onPostActivityCreation(activity);
-            spyOn(activity.mAppCompatController.getAppCompatAspectRatioOverrides());
+            spyOn(activity.mAppCompatController.getAspectRatioOverrides());
         }
 
         void checkShouldApplyUserFullscreenOverride(boolean expected) {
@@ -465,7 +465,7 @@
         }
 
         private AppCompatAspectRatioOverrides getTopActivityAppCompatAspectRatioOverrides() {
-            return activity().top().mAppCompatController.getAppCompatAspectRatioOverrides();
+            return activity().top().mAppCompatController.getAspectRatioOverrides();
         }
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatFocusOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatFocusOverridesTest.java
index d8f8453..5e49c8c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatFocusOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatFocusOverridesTest.java
@@ -188,7 +188,7 @@
         }
 
         void checkShouldSendFakeFocusOnTopActivity(boolean expected) {
-            Assert.assertEquals(activity().top().mAppCompatController.getAppCompatFocusOverrides()
+            Assert.assertEquals(activity().top().mAppCompatController.getFocusOverrides()
                     .shouldSendFakeFocus(), expected);
         }
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxPolicyTest.java
index e046f7c..b5ba111 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatLetterboxPolicyTest.java
@@ -364,7 +364,7 @@
 
         @NonNull
         private AppCompatAspectRatioPolicy getAspectRatioPolicy() {
-            return activity().top().mAppCompatController.getAppCompatAspectRatioPolicy();
+            return activity().top().mAppCompatController.getAspectRatioPolicy();
         }
 
         @NonNull
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 a0727a7..9e46c09 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationOverridesTest.java
@@ -275,7 +275,7 @@
         @Override
         void onPostActivityCreation(@NonNull ActivityRecord activity) {
             super.onPostActivityCreation(activity);
-            spyOn(activity.mAppCompatController.getAppCompatAspectRatioPolicy());
+            spyOn(activity.mAppCompatController.getAspectRatioPolicy());
         }
 
         // Useful to reduce timeout during tests
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 4faa714..f577c3a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatOrientationPolicyTest.java
@@ -555,8 +555,8 @@
         @Override
         void onPostActivityCreation(@NonNull ActivityRecord activity) {
             super.onPostActivityCreation(activity);
-            spyOn(activity.mAppCompatController.getAppCompatAspectRatioOverrides());
-            spyOn(activity.mAppCompatController.getAppCompatAspectRatioPolicy());
+            spyOn(activity.mAppCompatController.getAspectRatioOverrides());
+            spyOn(activity.mAppCompatController.getAspectRatioPolicy());
         }
 
         @Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java
index a5b2cb3..bfd533a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java
@@ -252,7 +252,7 @@
         @Override
         void onPostActivityCreation(@NonNull ActivityRecord activity) {
             super.onPostActivityCreation(activity);
-            spyOn(activity.mAppCompatController.getAppCompatAspectRatioPolicy());
+            spyOn(activity.mAppCompatController.getAspectRatioPolicy());
         }
 
         @Override
@@ -272,13 +272,13 @@
 
         void setIsLetterboxedForFixedOrientationAndAspectRatio(
                 boolean forFixedOrientationAndAspectRatio) {
-            when(activity().top().mAppCompatController.getAppCompatAspectRatioPolicy()
+            when(activity().top().mAppCompatController.getAspectRatioPolicy()
                     .isLetterboxedForFixedOrientationAndAspectRatio())
                         .thenReturn(forFixedOrientationAndAspectRatio);
         }
 
         void setIsLetterboxedForAspectRatioOnly(boolean forAspectRatio) {
-            when(activity().top().mAppCompatController.getAppCompatAspectRatioPolicy()
+            when(activity().top().mAppCompatController.getAspectRatioPolicy()
                     .isLetterboxedForAspectRatioOnly()).thenReturn(forAspectRatio);
         }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
index b6f4424..576d17a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
@@ -606,7 +606,7 @@
                 .setOnTop(true)
                 .setTask(task)
                 .build();
-        mActivity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
+        mActivity.mAppCompatController.getSizeCompatModePolicy().clearSizeCompatMode();
 
         spyOn(mActivity.mAppCompatController.getAppCompatCameraOverrides());
         spyOn(mActivity.info);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopAppCompatAspectRatioPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopAppCompatAspectRatioPolicyTests.java
index f4e1d49..fa7dcc8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopAppCompatAspectRatioPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopAppCompatAspectRatioPolicyTests.java
@@ -407,8 +407,8 @@
         @Override
         void onPostActivityCreation(@NonNull ActivityRecord activity) {
             super.onPostActivityCreation(activity);
-            spyOn(activity.mAppCompatController.getAppCompatAspectRatioOverrides());
-            spyOn(activity.mAppCompatController.getDesktopAppCompatAspectRatioPolicy());
+            spyOn(activity.mAppCompatController.getAspectRatioOverrides());
+            spyOn(activity.mAppCompatController.getDesktopAspectRatioPolicy());
         }
 
         void setDesiredAspectRatio(float aspectRatio) {
@@ -417,7 +417,7 @@
         }
 
         DesktopAppCompatAspectRatioPolicy getDesktopAppCompatAspectRatioPolicy() {
-            return getTopActivity().mAppCompatController.getDesktopAppCompatAspectRatioPolicy();
+            return getTopActivity().mAppCompatController.getDesktopAspectRatioPolicy();
         }
 
         float calculateAspectRatio() {
@@ -430,7 +430,7 @@
         }
 
         float getSplitScreenAspectRatio() {
-            return  getTopActivity().mAppCompatController.getAppCompatAspectRatioOverrides()
+            return  getTopActivity().mAppCompatController.getAspectRatioOverrides()
                     .getSplitScreenAspectRatio();
         }
 
@@ -442,7 +442,7 @@
 
         void checkHasMinAspectRatioOverride(boolean expected) {
             assertEquals(expected, this.activity().top().mAppCompatController
-                    .getDesktopAppCompatAspectRatioPolicy().hasMinAspectRatioOverride(
+                    .getDesktopAspectRatioPolicy().hasMinAspectRatioOverride(
                             this.activity().top().getTask()));
         }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
index 6d508ea..cdb51fc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
@@ -265,9 +265,9 @@
         final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_LANDSCAPE,
                 task, /* ignoreOrientationRequest */ true);
 
-        spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
+        spyOn(mActivity.mAppCompatController.getAspectRatioOverrides());
         doReturn(true).when(
-                        mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
+                        mActivity.mAppCompatController.getAspectRatioOverrides())
                 .isUserFullscreenOverrideEnabled();
 
         final int desiredWidth =
@@ -293,9 +293,9 @@
         final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_LANDSCAPE,
                 task, /* ignoreOrientationRequest */ true);
 
-        spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
+        spyOn(mActivity.mAppCompatController.getAspectRatioOverrides());
         doReturn(true).when(
-                        mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
+                        mActivity.mAppCompatController.getAspectRatioOverrides())
                 .isSystemOverrideToFullscreenEnabled();
 
         final int desiredWidth =
@@ -320,9 +320,9 @@
         final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT,
                 task, /* ignoreOrientationRequest */ true);
 
-        spyOn(activity.mAppCompatController.getDesktopAppCompatAspectRatioPolicy());
+        spyOn(activity.mAppCompatController.getDesktopAspectRatioPolicy());
         doReturn(LETTERBOX_ASPECT_RATIO).when(activity.mAppCompatController
-                .getDesktopAppCompatAspectRatioPolicy()).calculateAspectRatio(any());
+                .getDesktopAspectRatioPolicy()).calculateAspectRatio(any());
 
         final int desiredWidth =
                 (int) ((LANDSCAPE_DISPLAY_BOUNDS.height() / LETTERBOX_ASPECT_RATIO) + 0.5f);
@@ -424,7 +424,7 @@
                 (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
         final int desiredWidth =
                 (int) (desiredHeight / activity.mAppCompatController
-                        .getAppCompatAspectRatioOverrides().getSplitScreenAspectRatio());
+                        .getAspectRatioOverrides().getSplitScreenAspectRatio());
 
         assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
                 .setActivity(activity).calculate());
@@ -525,7 +525,7 @@
         final int desiredWidth =
                 (int) (PORTRAIT_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
         final int desiredHeight = (int) (desiredWidth * activity.mAppCompatController
-                .getAppCompatAspectRatioOverrides().getSplitScreenAspectRatio());
+                .getAspectRatioOverrides().getSplitScreenAspectRatio());
 
         assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
                 .setActivity(activity).calculate());
@@ -616,7 +616,7 @@
         final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_UNSPECIFIED,
                 task, /* ignoreOrientationRequest */ true);
         final float userAspectRatioOverrideValueSplitScreen = activity.mAppCompatController
-                .getAppCompatAspectRatioOverrides().getSplitScreenAspectRatio();
+                .getAspectRatioOverrides().getSplitScreenAspectRatio();
         applyUserMinAspectRatioOverride(activity, USER_MIN_ASPECT_RATIO_SPLIT_SCREEN,
                 userAspectRatioOverrideValueSplitScreen);
 
@@ -641,7 +641,7 @@
         final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_UNSPECIFIED,
                 task, /* ignoreOrientationRequest */ true);
         final float userAspectRatioOverrideValueDisplaySize = activity.mAppCompatController
-                .getAppCompatAspectRatioOverrides().getDisplaySizeMinAspectRatio();
+                .getAspectRatioOverrides().getDisplaySizeMinAspectRatio();
         applyUserMinAspectRatioOverride(activity, USER_MIN_ASPECT_RATIO_DISPLAY_SIZE,
                 userAspectRatioOverrideValueDisplaySize);
 
@@ -738,7 +738,7 @@
         final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_UNSPECIFIED,
                 task, /* ignoreOrientationRequest */ true);
         final float userAspectRatioOverrideValueSplitScreen = activity.mAppCompatController
-                .getAppCompatAspectRatioOverrides().getSplitScreenAspectRatio();
+                .getAspectRatioOverrides().getSplitScreenAspectRatio();
         applyUserMinAspectRatioOverride(activity, USER_MIN_ASPECT_RATIO_SPLIT_SCREEN,
                 userAspectRatioOverrideValueSplitScreen);
 
@@ -763,7 +763,7 @@
         final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_UNSPECIFIED,
                 task, /* ignoreOrientationRequest */ true);
         final float userAspectRatioOverrideValueDisplaySize = activity.mAppCompatController
-                .getAppCompatAspectRatioOverrides().getDisplaySizeMinAspectRatio();
+                .getAspectRatioOverrides().getDisplaySizeMinAspectRatio();
         applyUserMinAspectRatioOverride(activity, USER_MIN_ASPECT_RATIO_DISPLAY_SIZE,
                 userAspectRatioOverrideValueDisplaySize);
 
@@ -812,9 +812,9 @@
         final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT,
                 task, /* ignoreOrientationRequest */ true);
 
-        spyOn(activity.mAppCompatController.getDesktopAppCompatAspectRatioPolicy());
+        spyOn(activity.mAppCompatController.getDesktopAspectRatioPolicy());
         doReturn(LETTERBOX_ASPECT_RATIO).when(activity.mAppCompatController
-                .getDesktopAppCompatAspectRatioPolicy()).calculateAspectRatio(any());
+                .getDesktopAspectRatioPolicy()).calculateAspectRatio(any());
 
         final int desiredHeight =
                 (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
@@ -884,9 +884,9 @@
         final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT,
                 task, /* ignoreOrientationRequest */ true);
 
-        spyOn(activity.mAppCompatController.getAppCompatAspectRatioOverrides());
+        spyOn(activity.mAppCompatController.getAspectRatioOverrides());
         doReturn(true).when(
-                        activity.mAppCompatController.getAppCompatAspectRatioOverrides())
+                        activity.mAppCompatController.getAspectRatioOverrides())
                 .isUserFullscreenOverrideEnabled();
 
         final int desiredWidth =
@@ -912,9 +912,9 @@
         final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT,
                 task, /* ignoreOrientationRequest */ true);
 
-        spyOn(activity.mAppCompatController.getAppCompatAspectRatioOverrides());
+        spyOn(activity.mAppCompatController.getAspectRatioOverrides());
         doReturn(true).when(
-                        activity.mAppCompatController.getAppCompatAspectRatioOverrides())
+                        activity.mAppCompatController.getAspectRatioOverrides())
                 .isSystemOverrideToFullscreenEnabled();
 
         final int desiredWidth =
@@ -939,9 +939,9 @@
         final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_LANDSCAPE,
                 task, /* ignoreOrientationRequest */ true);
 
-        spyOn(activity.mAppCompatController.getDesktopAppCompatAspectRatioPolicy());
+        spyOn(activity.mAppCompatController.getDesktopAspectRatioPolicy());
         doReturn(LETTERBOX_ASPECT_RATIO).when(activity.mAppCompatController
-                .getDesktopAppCompatAspectRatioPolicy()).calculateAspectRatio(any());
+                .getDesktopAspectRatioPolicy()).calculateAspectRatio(any());
 
         final int desiredWidth = PORTRAIT_DISPLAY_BOUNDS.width()
                 - (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2);
@@ -989,9 +989,9 @@
         final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_LANDSCAPE,
                 task, /* ignoreOrientationRequest */ true);
 
-        spyOn(activity.mAppCompatController.getDesktopAppCompatAspectRatioPolicy());
+        spyOn(activity.mAppCompatController.getDesktopAspectRatioPolicy());
         doReturn(LETTERBOX_ASPECT_RATIO).when(activity.mAppCompatController
-                .getDesktopAppCompatAspectRatioPolicy()).calculateAspectRatio(any());
+                .getDesktopAspectRatioPolicy()).calculateAspectRatio(any());
 
         final int desiredWidth = PORTRAIT_DISPLAY_BOUNDS.width()
                 - (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2);
@@ -1294,7 +1294,7 @@
 
     private void setDesiredAspectRatio(ActivityRecord activity, float aspectRatio) {
         final DesktopAppCompatAspectRatioPolicy desktopAppCompatAspectRatioPolicy =
-                activity.mAppCompatController.getDesktopAppCompatAspectRatioPolicy();
+                activity.mAppCompatController.getDesktopAspectRatioPolicy();
         spyOn(desktopAppCompatAspectRatioPolicy);
         doReturn(aspectRatio).when(desktopAppCompatAspectRatioPolicy)
                 .getDesiredAspectRatio(any());
@@ -1304,7 +1304,7 @@
             float overrideValue) {
         // Set desired aspect ratio to be below minimum so override can take effect.
         final DesktopAppCompatAspectRatioPolicy desktopAppCompatAspectRatioPolicy =
-                activity.mAppCompatController.getDesktopAppCompatAspectRatioPolicy();
+                activity.mAppCompatController.getDesktopAspectRatioPolicy();
         spyOn(desktopAppCompatAspectRatioPolicy);
         doReturn(1f).when(desktopAppCompatAspectRatioPolicy)
                 .getDesiredAspectRatio(any());
@@ -1318,7 +1318,7 @@
 
         // Simulate user min aspect ratio override being set.
         final AppCompatAspectRatioOverrides appCompatAspectRatioOverrides =
-                activity.mAppCompatController.getAppCompatAspectRatioOverrides();
+                activity.mAppCompatController.getAspectRatioOverrides();
         spyOn(appCompatAspectRatioOverrides);
         doReturn(overrideValue).when(appCompatAspectRatioOverrides).getUserMinAspectRatio();
         doReturn(overrideCode).when(appCompatAspectRatioOverrides)
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index dfd10ec..d76a907 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1583,42 +1583,6 @@
                 is(Configuration.ORIENTATION_PORTRAIT));
     }
 
-    @Test
-    public void testHybridRotationAnimation() {
-        final DisplayContent displayContent = mDefaultDisplay;
-        final WindowState statusBar = newWindowBuilder("statusBar", TYPE_STATUS_BAR).build();
-        final WindowState navBar = newWindowBuilder("navBar", TYPE_NAVIGATION_BAR).build();
-        final WindowState app = newWindowBuilder("app", TYPE_BASE_APPLICATION).build();
-        final WindowState[] windows = { statusBar, navBar, app };
-        makeWindowVisible(windows);
-        final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
-        displayPolicy.addWindowLw(statusBar, statusBar.mAttrs);
-        displayPolicy.addWindowLw(navBar, navBar.mAttrs);
-        final ScreenRotationAnimation rotationAnim = new ScreenRotationAnimation(displayContent,
-                displayContent.getRotation());
-        spyOn(rotationAnim);
-        // Assume that the display rotation is changed so it is frozen in preparation for animation.
-        doReturn(true).when(rotationAnim).hasScreenshot();
-        displayContent.getDisplayRotation().setRotation((displayContent.getRotation() + 1) % 4);
-        displayContent.setRotationAnimation(rotationAnim);
-        // The fade rotation animation also starts to hide some non-app windows.
-        assertNotNull(displayContent.getAsyncRotationController());
-        assertTrue(statusBar.isAnimating(PARENTS, ANIMATION_TYPE_TOKEN_TRANSFORM));
-
-        for (WindowState w : windows) {
-            w.setOrientationChanging(true);
-        }
-        // The display only waits for the app window to unfreeze.
-        assertFalse(displayContent.shouldSyncRotationChange(statusBar));
-        assertFalse(displayContent.shouldSyncRotationChange(navBar));
-        assertTrue(displayContent.shouldSyncRotationChange(app));
-        // If all windows animated by fade rotation animation have done the orientation change,
-        // the animation controller should be cleared.
-        statusBar.setOrientationChanging(false);
-        navBar.setOrientationChanging(false);
-        assertNull(displayContent.getAsyncRotationController());
-    }
-
     @SetupWindows(addWindows = { W_ACTIVITY, W_WALLPAPER, W_STATUS_BAR, W_NAVIGATION_BAR,
             W_INPUT_METHOD, W_NOTIFICATION_SHADE })
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
index 347d1bc..a7e8ce9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
@@ -216,7 +216,7 @@
         final Rect activityBounds = new Rect(mFirstActivity.getBounds());
 
         // DAG is portrait (860x1200), so Task and Activity fill DAG.
-        assertThat(mFirstActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertThat(mFirstActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
         assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
         assertThat(taskBounds).isEqualTo(dagBounds);
@@ -241,7 +241,7 @@
                 new Rect(mFirstActivity.getConfiguration().windowConfiguration.getBounds());
 
         // DAG is landscape (1200x860), no fixed orientation letterbox
-        assertThat(mFirstActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertThat(mFirstActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
         assertThat(mFirstActivity.inSizeCompatMode()).isTrue();
         assertThat(newDagBounds.width()).isEqualTo(dagBounds.height());
@@ -266,12 +266,12 @@
         mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
 
         prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_NOSENSOR);
-        assertThat(mFirstActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertThat(mFirstActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
         assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
 
         rotateDisplay(mDisplay, ROTATION_90);
-        assertThat(mFirstActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertThat(mFirstActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
         assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
     }
@@ -289,7 +289,7 @@
 
         // DAG is portrait (860x1200), and activity is letterboxed for fixed orientation
         // (860x[860x860/1200=616]). Task fills DAG.
-        assertThat(mFirstActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertThat(mFirstActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio()).isTrue();
         assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
         assertThat(taskBounds).isEqualTo(dagBounds);
@@ -307,7 +307,7 @@
         mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
 
         prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_NOSENSOR);
-        assertThat(mFirstActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertThat(mFirstActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
     }
 
@@ -318,7 +318,7 @@
         mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
 
         prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_LOCKED);
-        assertThat(mFirstActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertThat(mFirstActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
     }
 
@@ -338,7 +338,7 @@
         final Rect newActivityBounds = new Rect(mFirstActivity.getBounds());
 
         // DAG is landscape (1200x860), no fixed orientation letterbox
-        assertThat(mFirstActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertThat(mFirstActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
         assertThat(mFirstActivity.inSizeCompatMode()).isTrue();
         assertThat(newDagBounds.width()).isEqualTo(dagBounds.height());
@@ -364,7 +364,7 @@
 
         rotateDisplay(mDisplay, ROTATION_90);
 
-        assertThat(mFirstActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertThat(mFirstActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
         assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
     }
@@ -527,7 +527,7 @@
         assertThat(mDisplay.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_LANDSCAPE);
         assertThat(mFirstRoot.getConfiguration().orientation).isEqualTo(ORIENTATION_PORTRAIT);
         assertThat(mFirstActivity.getConfiguration().orientation).isEqualTo(ORIENTATION_PORTRAIT);
-        assertThat(mFirstActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertThat(mFirstActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
         assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
 
@@ -540,14 +540,14 @@
         assertThat(mDisplay.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_PORTRAIT);
         assertThat(mSecondRoot.getConfiguration().orientation).isEqualTo(ORIENTATION_LANDSCAPE);
         assertThat(mSecondActivity.getConfiguration().orientation).isEqualTo(ORIENTATION_LANDSCAPE);
-        assertThat(mSecondActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertThat(mSecondActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio()).isFalse();
         assertThat(mSecondActivity.inSizeCompatMode()).isFalse();
 
         // First activity is letterboxed in portrait as requested.
         assertThat(mFirstRoot.getConfiguration().orientation).isEqualTo(ORIENTATION_LANDSCAPE);
         assertThat(mFirstActivity.getConfiguration().orientation).isEqualTo(ORIENTATION_PORTRAIT);
-        assertThat(mFirstActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertThat(mFirstActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio()).isTrue();
         assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index 7e62b89..fc4f54a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -535,7 +535,7 @@
         final Rect bounds = new Rect(task.getBounds());
         bounds.scale(0.5f);
         task.setBounds(bounds);
-        assertFalse(activity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertFalse(activity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertThat(task.autoRemoveRecents).isFalse();
     }
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 07ee09a..a84b374 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -673,7 +673,7 @@
         assertFalse(mActivity.mDisplayContent.shouldImeAttachedToApp());
 
         // Recompute the natural configuration without resolving size compat configuration.
-        mActivity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
+        mActivity.mAppCompatController.getSizeCompatModePolicy().clearSizeCompatMode();
         mActivity.onConfigurationChanged(mTask.getConfiguration());
         // It should keep non-attachable because the resolved bounds will be computed according to
         // the aspect ratio that won't match its parent bounds.
@@ -765,7 +765,7 @@
                         / originalBounds.width()));
 
         // Recompute the natural configuration in the new display.
-        mActivity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
+        mActivity.mAppCompatController.getSizeCompatModePolicy().clearSizeCompatMode();
         mActivity.ensureActivityConfiguration();
         // Because the display cannot rotate, the portrait activity will fit the short side of
         // display with keeping portrait bounds [200, 0 - 700, 1000] in center.
@@ -1143,11 +1143,11 @@
 
         // Simulate the user selecting the fullscreen user aspect ratio override
         spyOn(activity.mWmService.mAppCompatConfiguration);
-        spyOn(activity.mAppCompatController.getAppCompatAspectRatioOverrides());
+        spyOn(activity.mAppCompatController.getAspectRatioOverrides());
         doReturn(true).when(activity.mWmService.mAppCompatConfiguration)
                 .isUserAppAspectRatioFullscreenEnabled();
         doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN)
-                .when(activity.mAppCompatController.getAppCompatAspectRatioOverrides())
+                .when(activity.mAppCompatController.getAspectRatioOverrides())
                 .getUserMinAspectRatioOverrideCode();
         assertFalse(activity.shouldCreateAppCompatDisplayInsets());
     }
@@ -1165,9 +1165,9 @@
                 RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
 
         // Simulate the user selecting the fullscreen user aspect ratio override
-        spyOn(activity.mAppCompatController.getAppCompatAspectRatioOverrides());
+        spyOn(activity.mAppCompatController.getAspectRatioOverrides());
         doReturn(true).when(
-                activity.mAppCompatController.getAppCompatAspectRatioOverrides())
+                activity.mAppCompatController.getAspectRatioOverrides())
                     .isSystemOverrideToFullscreenEnabled();
         assertFalse(activity.shouldCreateAppCompatDisplayInsets());
     }
@@ -1187,7 +1187,7 @@
         prepareUnresizable(activity, /* maxAspect=*/ 1.5f, SCREEN_ORIENTATION_LANDSCAPE);
 
         // Activity max bounds should not be sandboxed, even though it is letterboxed.
-        assertTrue(activity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertTrue(activity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertThat(activity.getConfiguration().windowConfiguration.getMaxBounds())
                 .isEqualTo(activity.getDisplayArea().getBounds());
@@ -1229,7 +1229,7 @@
         prepareUnresizable(activity, /* maxAspect=*/ 1.5f, SCREEN_ORIENTATION_LANDSCAPE);
 
         // Activity max bounds should not be sandboxed, even though it is letterboxed.
-        assertTrue(activity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertTrue(activity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertThat(activity.getConfiguration().windowConfiguration.getMaxBounds())
                 .isEqualTo(activity.getDisplayArea().getBounds());
@@ -1253,7 +1253,7 @@
         prepareUnresizable(activity, /* maxAspect=*/ 1.5f, SCREEN_ORIENTATION_LANDSCAPE);
 
         // Activity max bounds should not be sandboxed, even though it is letterboxed.
-        assertTrue(activity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertTrue(activity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertThat(activity.getConfiguration().windowConfiguration.getMaxBounds())
                 .isEqualTo(activity.getDisplayArea().getBounds());
@@ -1507,7 +1507,7 @@
 
         // After changing the orientation to portrait the override should be applied.
         activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
-        activity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
+        activity.mAppCompatController.getSizeCompatModePolicy().clearSizeCompatMode();
 
         // The per-package override forces the activity into a 3:2 aspect ratio
         assertEquals(1200, activity.getBounds().height());
@@ -1531,7 +1531,7 @@
 
         // After changing the orientation to portrait the override should be applied.
         activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
-        activity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
+        activity.mAppCompatController.getSizeCompatModePolicy().clearSizeCompatMode();
 
         // The per-package override forces the activity into a 3:2 aspect ratio
         assertEquals(1200, activity.getBounds().height());
@@ -1554,7 +1554,7 @@
 
         // After changing the orientation to landscape the override shouldn't be applied.
         activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
-        activity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
+        activity.mAppCompatController.getSizeCompatModePolicy().clearSizeCompatMode();
 
         // The per-package override should have no effect
         assertEquals(1200, activity.getBounds().height());
@@ -1754,7 +1754,7 @@
         addWindowToActivity(mActivity);
 
         // App should launch in fullscreen.
-        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertFalse(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(mActivity.inSizeCompatMode());
 
@@ -1769,7 +1769,7 @@
         assertTrue(rotatedDisplayBounds.width() < rotatedDisplayBounds.height());
 
         // App should be in size compat.
-        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertFalse(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertDownScaled();
         assertThat(mActivity.inSizeCompatMode()).isTrue();
@@ -1882,7 +1882,7 @@
         assertTrue(displayBounds.width() > displayBounds.height());
 
         // App should launch in fixed orientation letterbox.
-        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(mActivity.inSizeCompatMode());
         assertActivityMaxBoundsSandboxed();
@@ -1913,7 +1913,7 @@
         assertTrue(displayBounds.width() > displayBounds.height());
 
         // App should launch in fixed orientation letterbox.
-        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(mActivity.inSizeCompatMode());
 
@@ -1943,7 +1943,7 @@
         assertTrue(displayBounds.width() > displayBounds.height());
 
         // App should launch in fixed orientation letterbox.
-        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(mActivity.inSizeCompatMode());
 
@@ -1973,7 +1973,7 @@
         assertTrue(displayBounds.width() > displayBounds.height());
 
         // App should launch in fixed orientation letterbox.
-        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(mActivity.inSizeCompatMode());
 
@@ -2070,7 +2070,7 @@
         assertTrue(displayBounds.width() > displayBounds.height());
 
         // App should launch in fixed orientation letterbox.
-        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(mActivity.inSizeCompatMode());
 
@@ -2094,7 +2094,7 @@
 
         // App should launch in fixed orientation letterbox.
         // Activity bounds should be 700x1400 with the ratio as the display.
-        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFitted();
         assertEquals(originalScreenWidthDp, mActivity.getConfiguration().smallestScreenWidthDp);
@@ -2134,7 +2134,7 @@
 
         // App should launch in fixed orientation letterbox.
         // Activity bounds should be 700x1400 with the ratio as the display.
-        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFitted();
         assertEquals(originalScreenWidthDp, mActivity.getConfiguration().smallestScreenWidthDp);
@@ -2169,7 +2169,7 @@
         final Rect activityBounds = new Rect(mActivity.getBounds());
 
         // App should launch in fixed orientation letterbox.
-        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         // Checking that there is no size compat mode.
         assertFitted();
@@ -2199,9 +2199,9 @@
                 .isUserAppAspectRatioFullscreenEnabled();
 
         // Set user aspect ratio override
-        spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
+        spyOn(mActivity.mAppCompatController.getAspectRatioOverrides());
         doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN)
-                .when(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
+                .when(mActivity.mAppCompatController.getAspectRatioOverrides())
                     .getUserMinAspectRatioOverrideCode();
 
         prepareMinAspectRatio(mActivity, 16 / 9f, SCREEN_ORIENTATION_PORTRAIT);
@@ -2224,9 +2224,9 @@
                 .isUserAppAspectRatioFullscreenEnabled();
 
         // Set user aspect ratio override
-        spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
+        spyOn(mActivity.mAppCompatController.getAspectRatioOverrides());
         doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN)
-                .when(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
+                .when(mActivity.mAppCompatController.getAspectRatioOverrides())
                     .getUserMinAspectRatioOverrideCode();
 
         prepareMinAspectRatio(mActivity, 16 / 9f, SCREEN_ORIENTATION_LANDSCAPE);
@@ -2244,9 +2244,9 @@
         final int displayWidth = mDisplayContent.mBaseDisplayWidth;
         final int displayHeight = mDisplayContent.mBaseDisplayHeight;
 
-        spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
+        spyOn(mActivity.mAppCompatController.getAspectRatioOverrides());
         doReturn(true).when(
-                mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
+                mActivity.mAppCompatController.getAspectRatioOverrides())
                     .isSystemOverrideToFullscreenEnabled();
 
         prepareMinAspectRatio(mActivity, 16 / 9f, SCREEN_ORIENTATION_PORTRAIT);
@@ -2264,9 +2264,9 @@
         final int displayWidth = mDisplayContent.mBaseDisplayWidth;
         final int displayHeight = mDisplayContent.mBaseDisplayHeight;
 
-        spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
+        spyOn(mActivity.mAppCompatController.getAspectRatioOverrides());
         doReturn(true).when(
-                mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
+                mActivity.mAppCompatController.getAspectRatioOverrides())
                     .isSystemOverrideToFullscreenEnabled();
 
         prepareMinAspectRatio(mActivity, 16 / 9f, SCREEN_ORIENTATION_LANDSCAPE);
@@ -2416,7 +2416,7 @@
             boolean enabled) {
         final ActivityRecord activity = getActivityBuilderWithoutTask().build();
         final DesktopAppCompatAspectRatioPolicy desktopAppCompatAspectRatioPolicy =
-                activity.mAppCompatController.getDesktopAppCompatAspectRatioPolicy();
+                activity.mAppCompatController.getDesktopAspectRatioPolicy();
         spyOn(desktopAppCompatAspectRatioPolicy);
         doReturn(enabled).when(desktopAppCompatAspectRatioPolicy)
                 .hasMinAspectRatioOverride(any());
@@ -2426,7 +2426,7 @@
         doReturn(enabled).when(activity.mWmService.mAppCompatConfiguration)
                 .isUserAppAspectRatioSettingsEnabled();
         final AppCompatAspectRatioOverrides aspectRatioOverrides =
-                activity.mAppCompatController.getAppCompatAspectRatioOverrides();
+                activity.mAppCompatController.getAspectRatioOverrides();
         spyOn(aspectRatioOverrides);
         // Set user aspect ratio override.
         doReturn(aspectRatio).when(aspectRatioOverrides).getUserMinAspectRatioOverrideCode();
@@ -2703,7 +2703,7 @@
         final Rect activityBounds = new Rect(mActivity.getBounds());
 
         // App should launch in fixed orientation letterbox.
-        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         // Checking that there is no size compat mode.
         assertFitted();
@@ -2747,7 +2747,7 @@
         assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
 
         // App should launch in fixed orientation letterbox.
-        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         // Checking that there is no size compat mode.
         assertFitted();
@@ -2782,7 +2782,7 @@
         assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
 
         // App should launch in fixed orientation letterbox.
-        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         // Checking that there is no size compat mode.
         assertFitted();
@@ -2808,7 +2808,7 @@
         prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
 
         // App should launch in fixed orientation letterbox.
-        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         // Checking that there is no size compat mode.
         assertFitted();
@@ -2834,7 +2834,7 @@
         prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
 
         // App should launch in fixed orientation letterbox.
-        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         // Checking that there is no size compat mode.
         assertFitted();
@@ -2863,7 +2863,7 @@
         assertTrue(displayBounds.width() < displayBounds.height());
 
         // App should be in size compat.
-        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertFalse(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertThat(mActivity.inSizeCompatMode()).isTrue();
         assertEquals(activityBounds.width(), newActivityBounds.width());
@@ -2880,7 +2880,7 @@
         prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);
 
         // App should launch in fullscreen.
-        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertFalse(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(mActivity.inSizeCompatMode());
         // Activity inherits max bounds from TaskDisplayArea.
@@ -2894,7 +2894,7 @@
         assertTrue(rotatedDisplayBounds.width() > rotatedDisplayBounds.height());
 
         // App should be in size compat.
-        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertFalse(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertDownScaled();
         assertThat(mActivity.inSizeCompatMode()).isTrue();
@@ -2915,7 +2915,7 @@
         // Portrait fixed app without max aspect.
         prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);
 
-        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(mActivity.inSizeCompatMode());
 
@@ -2938,7 +2938,7 @@
 
         // Task and display bounds should be equal while activity should be letterboxed and
         // has 700x1400 bounds with the ratio as the display.
-        assertTrue(newActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertTrue(newActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(newActivity.inSizeCompatMode());
         // Activity max bounds are sandboxed due to size compat mode.
@@ -2959,7 +2959,7 @@
         // Portrait fixed app without max aspect.
         prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);
 
-        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(mActivity.inSizeCompatMode());
 
@@ -2980,7 +2980,7 @@
         // Portrait fixed app without max aspect.
         prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);
 
-        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(mActivity.inSizeCompatMode());
 
@@ -3011,7 +3011,7 @@
         assertActivityMaxBoundsSandboxed();
 
         // Activity bounds should be (1400 / 1.3 = 1076)x1400 with the app requested ratio.
-        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(newActivity.inSizeCompatMode());
         assertEquals(displayBounds.height(), newActivityBounds.height());
@@ -3030,7 +3030,7 @@
         prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);
         clearInvocations(mActivity);
 
-        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(mActivity.inSizeCompatMode());
 
@@ -3038,7 +3038,7 @@
         rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
 
         // App should be in size compat.
-        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertFalse(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertThat(mActivity.inSizeCompatMode()).isTrue();
         // Activity max bounds are sandboxed due to size compat mode.
@@ -3050,10 +3050,10 @@
 
         // App still in size compat, and the bounds don't change.
         final AppCompatSizeCompatModePolicy scmPolicy = mActivity.mAppCompatController
-                .getAppCompatSizeCompatModePolicy();
+                .getSizeCompatModePolicy();
         spyOn(scmPolicy);
         verify(scmPolicy, never()).clearSizeCompatMode();
-        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertFalse(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertThat(mActivity.inSizeCompatMode()).isTrue();
         assertEquals(activityBounds, mActivity.getBounds());
@@ -3071,7 +3071,7 @@
         prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);
 
         // In fixed orientation letterbox
-        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(mActivity.inSizeCompatMode());
         assertActivityMaxBoundsSandboxed();
@@ -3080,7 +3080,7 @@
         rotateDisplay(display, ROTATION_90);
 
         // App should be in size compat.
-        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertFalse(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertThat(mActivity.inSizeCompatMode()).isTrue();
         assertActivityMaxBoundsSandboxed();
@@ -3089,7 +3089,7 @@
         rotateDisplay(display, ROTATION_180);
 
         // In activity letterbox
-        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(mActivity.inSizeCompatMode());
         assertActivityMaxBoundsSandboxed();
@@ -3108,7 +3108,7 @@
         prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_LANDSCAPE);
 
         // In fixed orientation letterbox
-        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(mActivity.inSizeCompatMode());
         assertActivityMaxBoundsSandboxed();
@@ -3117,7 +3117,7 @@
         rotateDisplay(display, ROTATION_90);
 
         // App should be in size compat.
-        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertFalse(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertDownScaled();
         assertActivityMaxBoundsSandboxed();
@@ -3126,7 +3126,7 @@
         rotateDisplay(display, ROTATION_180);
 
         // In fixed orientation letterbox
-        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(mActivity.inSizeCompatMode());
         assertActivityMaxBoundsSandboxed();
@@ -3325,7 +3325,7 @@
         assertEquals(ORIENTATION_PORTRAIT, mTask.getConfiguration().orientation);
         assertEquals(ORIENTATION_LANDSCAPE, mActivity.getConfiguration().orientation);
         assertFitted();
-        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertActivityMaxBoundsSandboxed();
 
@@ -3352,7 +3352,7 @@
 
         // Resizable activity is not in size compat mode but in the letterbox for fixed orientation.
         assertFitted();
-        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
     }
 
@@ -3389,7 +3389,7 @@
         assertEquals(ORIENTATION_PORTRAIT, mTask.getConfiguration().orientation);
         assertEquals(ORIENTATION_PORTRAIT, mActivity.getConfiguration().orientation);
         assertFitted();
-        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertFalse(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertActivityMaxBoundsSandboxed();
 
@@ -3889,14 +3889,15 @@
 
     private void recomputeNaturalConfigurationOfUnresizableActivity() {
         // Recompute the natural configuration of the non-resizable activity and the split screen.
-        mActivity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
+        mActivity.mAppCompatController.getSizeCompatModePolicy().clearSizeCompatMode();
 
         // Draw letterbox.
-        mActivity.setVisible(false);
-        mActivity.mDisplayContent.prepareAppTransition(WindowManager.TRANSIT_OPEN);
-        mActivity.mDisplayContent.mOpeningApps.add(mActivity);
+        mActivity.ensureActivityConfiguration();
         addWindowToActivity(mActivity);
         mActivity.mRootWindowContainer.performSurfacePlacement();
+
+        // Verify mAppCompatDisplayInsets is created after recomputing.
+        assertTrue(mActivity.providesMaxBounds());
     }
 
     private void assertLetterboxSurfacesDrawnBetweenActivityAndParentBounds(Rect parentBounds) {
@@ -4031,7 +4032,7 @@
         // orientation is not respected with insets as insets have been decoupled.
         final Rect appBounds = activity.getWindowConfiguration().getAppBounds();
         final Rect displayBounds = display.getBounds();
-        assertFalse(activity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertFalse(activity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertNotNull(appBounds);
         assertEquals(displayBounds.width(), appBounds.width());
@@ -4063,7 +4064,7 @@
 
         final Rect bounds = activity.getBounds();
         // Activity should be letterboxed and should have portrait app bounds
-        assertTrue(activity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertTrue(activity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertTrue(bounds.height() > bounds.width());
     }
@@ -4098,7 +4099,7 @@
         assertNotNull(activity.getAppCompatDisplayInsets());
         // Activity is not letterboxed for fixed orientation because orientation is respected
         // with insets, and should not be in size compat mode
-        assertFalse(activity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertFalse(activity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(activity.inSizeCompatMode());
     }
@@ -4357,7 +4358,7 @@
         Configuration parentConfig = mActivity.getParent().getConfiguration();
 
         final AppCompatAspectRatioOverrides aspectRatioOverrides =
-                mActivity.mAppCompatController.getAppCompatAspectRatioOverrides();
+                mActivity.mAppCompatController.getAspectRatioOverrides();
         float actual = aspectRatioOverrides.getFixedOrientationLetterboxAspectRatio(parentConfig);
         float expected = aspectRatioOverrides.getSplitScreenAspectRatio();
 
@@ -4497,14 +4498,14 @@
                 .isUserAppAspectRatioSettingsEnabled();
         final AppCompatController appCompatController = mActivity.mAppCompatController;
         final AppCompatAspectRatioOverrides aspectRatioOverrides =
-                appCompatController.getAppCompatAspectRatioOverrides();
+                appCompatController.getAspectRatioOverrides();
         spyOn(aspectRatioOverrides);
         // Set user aspect ratio override.
         doReturn(USER_MIN_ASPECT_RATIO_16_9).when(aspectRatioOverrides)
                 .getUserMinAspectRatioOverrideCode();
 
         prepareLimitedBounds(mActivity, SCREEN_ORIENTATION_PORTRAIT, /* isUnresizable= */ false);
-        assertFalse(appCompatController.getAppCompatAspectRatioPolicy().isAspectRatioApplied());
+        assertFalse(appCompatController.getAspectRatioPolicy().isAspectRatioApplied());
     }
 
     @Test
@@ -4520,14 +4521,14 @@
                 .isUserAppAspectRatioSettingsEnabled();
         final AppCompatController appCompatController = mActivity.mAppCompatController;
         final AppCompatAspectRatioOverrides aspectRatioOverrides =
-                appCompatController.getAppCompatAspectRatioOverrides();
+                appCompatController.getAspectRatioOverrides();
         spyOn(aspectRatioOverrides);
         // Set user aspect ratio override.
         doReturn(USER_MIN_ASPECT_RATIO_16_9).when(aspectRatioOverrides)
                 .getUserMinAspectRatioOverrideCode();
 
         prepareLimitedBounds(mActivity, SCREEN_ORIENTATION_PORTRAIT, /* isUnresizable= */ true);
-        assertTrue(appCompatController.getAppCompatAspectRatioPolicy().isAspectRatioApplied());
+        assertTrue(appCompatController.getAspectRatioPolicy().isAspectRatioApplied());
     }
 
     @Test
@@ -4540,8 +4541,7 @@
         setUpDisplaySizeWithApp(2500, 1600, taskBuilder);
         prepareLimitedBounds(mActivity, SCREEN_ORIENTATION_PORTRAIT, /* isUnresizable= */ false);
 
-        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
-                .isAspectRatioApplied());
+        assertFalse(mActivity.mAppCompatController.getAspectRatioPolicy().isAspectRatioApplied());
     }
 
     @Test
@@ -4554,8 +4554,7 @@
         setUpDisplaySizeWithApp(2500, 1600, taskBuilder);
         prepareLimitedBounds(mActivity, SCREEN_ORIENTATION_PORTRAIT, /* isUnresizable= */ true);
 
-        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
-                .isAspectRatioApplied());
+        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy().isAspectRatioApplied());
     }
 
     private void assertVerticalPositionForDifferentDisplayConfigsForLandscapeActivity(
@@ -4604,7 +4603,7 @@
         verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED);
 
         prepareUnresizable(mActivity, /* maxAspect= */ 2, SCREEN_ORIENTATION_PORTRAIT);
-        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertFalse(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(mActivity.inSizeCompatMode());
         assertTrue(mActivity.areBoundsLetterboxed());
@@ -4621,7 +4620,7 @@
         // ActivityRecord#resolveSizeCompatModeConfiguration because mCompatDisplayInsets aren't
         // null but activity doesn't enter size compat mode. Checking that areBoundsLetterboxed()
         // still returns true because of the aspect ratio restrictions.
-        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertFalse(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(mActivity.inSizeCompatMode());
         assertTrue(mActivity.areBoundsLetterboxed());
@@ -4649,7 +4648,7 @@
 
         prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
 
-        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertFalse(mActivity.inSizeCompatMode());
         assertTrue(mActivity.areBoundsLetterboxed());
@@ -4668,7 +4667,7 @@
 
         rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
 
-        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertFalse(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertTrue(mActivity.inSizeCompatMode());
         assertTrue(mActivity.areBoundsLetterboxed());
@@ -4730,7 +4729,7 @@
         prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
 
         assertFalse(mActivity.isEligibleForLetterboxEducation());
-        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
     }
 
@@ -4789,7 +4788,7 @@
         prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
 
         assertTrue(mActivity.isEligibleForLetterboxEducation());
-        assertTrue(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
     }
 
@@ -4804,7 +4803,7 @@
         rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
 
         assertTrue(mActivity.isEligibleForLetterboxEducation());
-        assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertFalse(mActivity.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertTrue(mActivity.inSizeCompatMode());
     }
@@ -5007,13 +5006,13 @@
         assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, mActivity.getOverrideOrientation());
         assertEquals(mActivity.getTask().getBounds(), mActivity.getBounds());
         final AppCompatAspectRatioPolicy aspectRatioPolicy = mActivity.mAppCompatController
-                .getAppCompatAspectRatioPolicy();
+                .getAspectRatioPolicy();
         assertEquals(0, aspectRatioPolicy.getMaxAspectRatio(), 0 /* delta */);
         assertEquals(0, aspectRatioPolicy.getMinAspectRatio(), 0 /* delta */);
 
         // Compat override can still take effect.
         final AppCompatAspectRatioOverrides aspectRatioOverrides =
-                mActivity.mAppCompatController.getAppCompatAspectRatioOverrides();
+                mActivity.mAppCompatController.getAspectRatioOverrides();
         spyOn(aspectRatioOverrides);
         doReturn(true).when(aspectRatioOverrides).shouldOverrideMinAspectRatio();
         assertEquals(minAspect, aspectRatioPolicy.getMinAspectRatio(), 0 /* delta */);
@@ -5090,7 +5089,7 @@
         assertEquals(origDensity, mActivity.getConfiguration().densityDpi);
 
         // Activity should exit size compat with new density.
-        mActivity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
+        mActivity.mAppCompatController.getSizeCompatModePolicy().clearSizeCompatMode();
 
         assertFitted();
         assertEquals(newDensity, mActivity.getConfiguration().densityDpi);
@@ -5274,7 +5273,7 @@
             activity.setRequestedOrientation(screenOrientation);
         }
         // Make sure to use the provided configuration to construct the size compat fields.
-        activity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode();
+        activity.mAppCompatController.getSizeCompatModePolicy().clearSizeCompatMode();
         activity.ensureActivityConfiguration();
         // Make sure the display configuration reflects the change of activity.
         if (activity.mDisplayContent.updateOrientation()) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index a12831e..a95093d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -43,6 +43,7 @@
 import static org.mockito.Mockito.when;
 import static org.mockito.Mockito.withSettings;
 
+import android.annotation.Nullable;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityThread;
 import android.app.AppOpsManager;
@@ -67,7 +68,6 @@
 import android.os.PowerManager;
 import android.os.PowerManagerInternal;
 import android.os.PowerSaveState;
-import android.os.StrictMode;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.DeviceConfig;
@@ -75,7 +75,6 @@
 import android.view.DisplayInfo;
 import android.view.InputChannel;
 import android.view.SurfaceControl;
-import android.window.ConfigurationChangeSetting;
 
 import com.android.dx.mockito.inline.extended.StaticMockitoSession;
 import com.android.internal.os.BackgroundThread;
@@ -96,11 +95,9 @@
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.UserManagerService;
 import com.android.server.policy.PermissionPolicyInternal;
-import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.testutils.StubTransaction;
 import com.android.server.uri.UriGrantsManagerInternal;
-import com.android.window.flags.Flags;
 
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
@@ -144,13 +141,15 @@
     private ActivityTaskManagerService mAtmService;
     private WindowManagerService mWmService;
     private InputManagerService mImService;
-    private Runnable mOnBeforeServicesCreated;
+    @Nullable
+    private final Runnable mOnBeforeServicesCreated;
+
     /**
      * Spied {@link SurfaceControl.Transaction} class than can be used to verify calls.
      */
     SurfaceControl.Transaction mTransaction;
 
-    public SystemServicesTestRule(Runnable onBeforeServicesCreated) {
+    public SystemServicesTestRule(@Nullable Runnable onBeforeServicesCreated) {
         mOnBeforeServicesCreated = onBeforeServicesCreated;
     }
 
@@ -398,15 +397,13 @@
     }
 
     private void setUpWindowManagerService() {
-        TestWindowManagerPolicy wmPolicy = new TestWindowManagerPolicy();
-        TestDisplayWindowSettingsProvider testDisplayWindowSettingsProvider =
-                new TestDisplayWindowSettingsProvider();
-        // Suppress StrictMode violation (DisplayWindowSettings) to avoid log flood.
-        DisplayThread.getHandler().post(StrictMode::allowThreadDiskWritesMask);
-        mWmService = WindowManagerService.main(
-                mContext, mImService, false, wmPolicy, mAtmService,
-                testDisplayWindowSettingsProvider, StubTransaction::new,
-                MockSurfaceControlBuilder::new, mAppCompat);
+        // Use a spied Transaction class to prevent native code calls and verify interactions.
+        mTransaction = spy(StubTransaction.class);
+
+        mWmService = WindowManagerServiceTestSupport.setUpService(mContext, mImService,
+                new TestWindowManagerPolicy(), mAtmService, new TestDisplayWindowSettingsProvider(),
+                mTransaction, new MockSurfaceControlBuilder(), mAppCompat);
+
         spyOn(mWmService);
         spyOn(mWmService.mRoot);
         // Invoked during {@link ActivityStack} creation.
@@ -418,10 +415,6 @@
         spyOn(mWmService.mDisplayWindowSettings);
         spyOn(mWmService.mDisplayWindowSettingsProvider);
 
-        // Setup factory classes to prevent calls to native code.
-        mTransaction = spy(StubTransaction.class);
-        // Return a spied Transaction class than can be used to verify calls.
-        mWmService.mTransactionFactory = () -> mTransaction;
         mWmService.mSurfaceAnimationRunner = new SurfaceAnimationRunner(
                 null, null, mTransaction, mWmService.mPowerManagerInternal);
 
@@ -488,12 +481,12 @@
     }
 
     private static void tearDownLocalServices() {
+        WindowManagerServiceTestSupport.tearDownService();
+
         LocalServices.removeServiceForTest(DisplayManagerInternal.class);
         LocalServices.removeServiceForTest(PowerManagerInternal.class);
         LocalServices.removeServiceForTest(ActivityManagerInternal.class);
         LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
-        LocalServices.removeServiceForTest(WindowManagerInternal.class);
-        LocalServices.removeServiceForTest(WindowManagerPolicy.class);
         LocalServices.removeServiceForTest(PackageManagerInternal.class);
         LocalServices.removeServiceForTest(UriGrantsManagerInternal.class);
         LocalServices.removeServiceForTest(PermissionPolicyInternal.class);
@@ -501,12 +494,7 @@
         LocalServices.removeServiceForTest(UsageStatsManagerInternal.class);
         LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
         LocalServices.removeServiceForTest(UserManagerInternal.class);
-        LocalServices.removeServiceForTest(ImeTargetVisibilityPolicy.class);
         LocalServices.removeServiceForTest(GrammaticalInflectionManagerInternal.class);
-        if (Flags.condenseConfigurationChangeForSimpleMode()) {
-            LocalServices.removeServiceForTest(
-                    ConfigurationChangeSetting.ConfigurationChangeSettingInternal.class);
-        }
     }
 
     Description getDescription() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index ef58498..546ecc6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -36,6 +36,7 @@
 import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_DIM_ON_TASK;
 import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
 import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE;
@@ -1911,6 +1912,53 @@
                 OP_TYPE_REORDER_TO_TOP_OF_TASK);
     }
 
+    @Test
+    public void testApplyTransaction_setCanAffectSystemUiFlags() {
+        mController.unregisterOrganizer(mIOrganizer);
+        registerTaskFragmentOrganizer(mIOrganizer, true /* isSystemOrganizer */);
+
+        final Task task = createTask(mDisplayContent);
+        final TaskFragment tf = createTaskFragment(task);
+
+        // Setting the flag to false.
+        TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+                OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS).setBooleanValue(false).build();
+        mTransaction.addTaskFragmentOperation(tf.getFragmentToken(), operation);
+
+        assertApplyTransactionAllowed(mTransaction);
+
+        verify(tf).setCanAffectSystemUiFlags(false);
+
+        // Setting the flag back to true.
+        operation = new TaskFragmentOperation.Builder(
+                OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS).setBooleanValue(true).build();
+        mTransaction.addTaskFragmentOperation(tf.getFragmentToken(), operation);
+
+        assertApplyTransactionAllowed(mTransaction);
+
+        verify(tf).setCanAffectSystemUiFlags(true);
+    }
+
+    @Test
+    public void testApplyTransaction_setCanAffectSystemUiFlags_failsIfNotSystemOrganizer() {
+        final Task task = createTask(mDisplayContent);
+        final TaskFragment tf = createTaskFragment(task);
+
+        TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+                OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS).setBooleanValue(false).build();
+        mTransaction
+                .addTaskFragmentOperation(tf.getFragmentToken(), operation)
+                .setErrorCallbackToken(mErrorToken);
+
+        assertApplyTransactionAllowed(mTransaction);
+
+        // The pending event will be dispatched on the handler (from requestTraversal).
+        waitHandlerIdle(mWm.mAnimationHandler);
+
+        assertTaskFragmentErrorTransaction(OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS,
+                SecurityException.class);
+    }
+
     @NonNull
     private ActivityRecord setupUntrustedEmbeddingPipReparent() {
         final int pid = Binder.getCallingPid();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 2997173..0d97724 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -759,13 +759,13 @@
         // Assert fixed orientation request is ignored for activity in ActivityEmbedding split.
         activity0.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
 
-        assertFalse(activity0.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertFalse(activity0.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertEquals(SCREEN_ORIENTATION_UNSET, task.getOrientation());
 
         activity1.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT);
 
-        assertFalse(activity1.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertFalse(activity1.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertEquals(SCREEN_ORIENTATION_UNSET, task.getOrientation());
 
@@ -773,9 +773,9 @@
         mDisplayContent.setIgnoreOrientationRequest(true);
         task.onConfigurationChanged(task.getParent().getConfiguration());
 
-        assertFalse(activity0.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertFalse(activity0.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
-        assertFalse(activity1.mAppCompatController.getAppCompatAspectRatioPolicy()
+        assertFalse(activity1.mAppCompatController.getAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
         assertEquals(SCREEN_ORIENTATION_UNSET, task.getOrientation());
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
index 1e0cef0..1e16c97 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
@@ -113,7 +113,7 @@
         mSnapshotPersistQueue = new SnapshotPersistQueue();
         PersistInfoProvider provider =
                 TaskSnapshotController.createPersistInfoProvider(mWm, userId -> FILES_DIR);
-        mPersister = new TaskSnapshotPersister(mSnapshotPersistQueue, provider);
+        mPersister = new TaskSnapshotPersister(mSnapshotPersistQueue, provider, false);
         mLoader = new AppSnapshotLoader(provider);
         mSnapshotPersistQueue.start();
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 1febc9f..38d3d2f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -684,9 +684,7 @@
                 .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
         Task task = rootTask.getBottomMostTask();
         task.getRootActivity().setOrientation(SCREEN_ORIENTATION_UNSPECIFIED);
-        DisplayInfo info = new DisplayInfo();
-        display.mDisplay.getDisplayInfo(info);
-        final Rect fullScreenBounds = new Rect(0, 0, info.logicalWidth, info.logicalHeight);
+        final Rect fullScreenBounds = new Rect(display.getBounds());
         final Rect freeformBounds = new Rect(fullScreenBounds);
         freeformBounds.inset((int) (freeformBounds.width() * 0.2),
                 (int) (freeformBounds.height() * 0.2));
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java
index f1180ff..9cd302e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java
@@ -354,7 +354,7 @@
                     ta.launchTransparentActivityInTask();
                     a.assertNotNullOnTopActivity(ActivityRecord::getAppCompatDisplayInsets);
                     a.applyToTopActivity((top) -> {
-                        top.mAppCompatController.getAppCompatSizeCompatModePolicy()
+                        top.mAppCompatController.getSizeCompatModePolicy()
                                 .clearSizeCompatMode();
                     });
                     a.assertNullOnTopActivity(ActivityRecord::getAppCompatDisplayInsets);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTestSupport.kt b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTestSupport.kt
new file mode 100644
index 0000000..a165d20
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTestSupport.kt
@@ -0,0 +1,112 @@
+/*
+ * 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.server.wm
+
+import android.content.Context
+import android.os.StrictMode
+import android.view.SurfaceControl
+import android.window.ConfigurationChangeSetting
+import com.android.server.DisplayThread
+import com.android.server.LocalServices
+import com.android.server.input.InputManagerService
+import com.android.server.policy.WindowManagerPolicy
+import com.android.window.flags.Flags
+
+/**
+ * Provides support for tests that require a [WindowManagerService].
+ *
+ * It provides functionalities for setting up and tearing down the service with proper dependencies,
+ * which can be used across different test modules.
+ */
+object WindowManagerServiceTestSupport {
+
+    /**
+     * Sets up and initializes a [WindowManagerService] instance with the provided dependencies.
+     *
+     * This method constructs a [WindowManagerService] using the provided dependencies for testing.
+     * It's marked as `internal` due to the package-private classes [DisplayWindowSettingsProvider]
+     * and [AppCompatConfiguration]. The `@JvmName` annotation is used to bypass name mangling and
+     * allow access from Java via `WindowManagerServiceTestSupport.setUpService`.
+     *
+     * **Important:** Before calling this method, ensure that any previous [WindowManagerService]
+     * instance and its related services are properly torn down. In your test's setup, it is
+     * recommended to call [tearDownService] before calling [setUpService] to handle cases where a
+     * previous test might have crashed and left services in an inconsistent state. This is crucial
+     * for test reliability.
+     *
+     * Example usage in a test's `setUp()` method:
+     * ```
+     * @Before
+     * fun setUp() {
+     *     WindowManagerServiceTestSupport.tearDownService() // Clean up before setup.
+     *     mWindowManagerService = WindowManagerServiceTestSupport.setUpService(...)
+     *     // ... rest of your setup logic ...
+     * }
+     * ```
+     *
+     * @param context the [Context] for the service.
+     * @param im the [InputManagerService] to use.
+     * @param policy the [WindowManagerPolicy] to use.
+     * @param atm the [ActivityTaskManagerService] to use.
+     * @param displayWindowSettingsProvider the [DisplayWindowSettingsProvider] to use.
+     * @param surfaceControlTransaction the [SurfaceControl.Transaction] instance to use.
+     * @param surfaceControlBuilder the [SurfaceControl.Builder] instance to use.
+     * @param appCompat the [AppCompatConfiguration] to use.
+     * @return the created [WindowManagerService] instance.
+     */
+    @JvmStatic
+    @JvmName("setUpService")
+    internal fun setUpService(
+        context: Context,
+        im: InputManagerService,
+        policy: WindowManagerPolicy,
+        atm: ActivityTaskManagerService,
+        displayWindowSettingsProvider: DisplayWindowSettingsProvider,
+        surfaceControlTransaction: SurfaceControl.Transaction,
+        surfaceControlBuilder: SurfaceControl.Builder,
+        appCompat: AppCompatConfiguration,
+    ): WindowManagerService {
+        // Suppress StrictMode violation (DisplayWindowSettings) to avoid log flood.
+        DisplayThread.getHandler().post { StrictMode.allowThreadDiskWritesMask() }
+
+        return WindowManagerService.main(
+            context,
+            im,
+            false, /* showBootMsgs */
+            policy,
+            atm,
+            displayWindowSettingsProvider,
+            { surfaceControlTransaction },
+            { surfaceControlBuilder },
+            appCompat,
+        )
+    }
+
+    /** Tears down the [WindowManagerService] and removes related local services. */
+    @JvmStatic
+    fun tearDownService() {
+        LocalServices.removeServiceForTest(WindowManagerPolicy::class.java)
+        LocalServices.removeServiceForTest(WindowManagerInternal::class.java)
+        LocalServices.removeServiceForTest(ImeTargetVisibilityPolicy::class.java)
+
+        if (Flags.condenseConfigurationChangeForSimpleMode()) {
+            LocalServices.removeServiceForTest(
+                ConfigurationChangeSetting.ConfigurationChangeSettingInternal::class.java,
+            )
+        }
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index ab9abfc..f6f473b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -855,7 +855,6 @@
         startingApp.updateResizingWindowIfNeeded();
         assertTrue(mWm.mResizingWindows.contains(startingApp));
         assertTrue(startingApp.isDrawn());
-        assertFalse(startingApp.getOrientationChanging());
 
         // Even if the display is frozen, invisible requested window should not be affected.
         mWm.startFreezingDisplay(0, 0, mDisplayContent);
@@ -873,7 +872,6 @@
         win.updateResizingWindowIfNeeded();
 
         assertThat(mWm.mResizingWindows).contains(win);
-        assertTrue(win.getOrientationChanging());
 
         mWm.mResizingWindows.remove(win);
         spyOn(win.mClient);
@@ -892,7 +890,6 @@
         // Even "resized" throws remote exception, it is still considered as reported. So the window
         // shouldn't be resized again (which may block unfreeze in real case).
         assertThat(mWm.mResizingWindows).doesNotContain(win);
-        assertFalse(win.getOrientationChanging());
     }
 
     @Test
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
index 5db02e3..7b1e4cc 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
@@ -38,6 +38,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -284,8 +285,11 @@
 
         // Stop the monitor
         mIpSecPacketLossDetector.close();
+        mIpSecPacketLossDetector.close();
         verifyStopped();
-        verify(mIpSecTransform).close();
+
+        verify(mIpSecTransform, never()).close();
+        verify(mContext).unregisterReceiver(any());
     }
 
     @Test